<![CDATA[Event-Driven by Oskar Dudycz]]>https://event-driven.ioGatsbyJSWed, 18 Mar 2026 17:02:00 GMT<![CDATA[Interactive Rubber Ducking with GenAI]]>https://event-driven.io/en/interactive_rubber_ducking_with_gen_ai/https://event-driven.io/en/interactive_rubber_ducking_with_gen_ai/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a08c97f5281d31661ba3946fbc3b1ec0/a331c/2026-03-16-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADsUlEQVQ4yxXPjTvUBwDA8V+FUXucl7yelFmu4ygvxcmMc5zXw90KP1xejuQl0U15CV1Uzz29iLNhWiF7ppcn1TzLabLn2fbsCSWzhdmTZ+tpf8Z3zz7/wUcoqyrl81YTtrnnvFpZ5v2/73j2dILZ6fu8fbvGxOQQU9/dYurBCKViDoci5XzZe5GFn6d5Pn0P25Nxnj4aZdDaTV29EeGy1crgxAPMN3rpvHadjMJCYjVq2jqbmZ37nvOXGzGW6zhbmU/OYQVe7o6EKgKoqjpGZlYyp0+V83hiENGQx5nmBoSRu9/y5u93vN74i8XVNQ6mprLNTYIyMY5nP9qwXGrhM20qb2z3udskUpMcSlGsjAORckJCP0KXnchwXxdGo4hGq0EYHLnF/OoaPy0tMTe/QHy2Fn9FEI7uzuiLjnL8ZCVRnyjpMVXwwFRAvxiLJU9J+kEZIWEh5OrU3B6w0N3ZSM91M8L43W9Y/ec9L9c3efHnJod0R9ixJwAHVwmpWWoKS7XIo8LRxEfzqLmMsxlh1KlkxPt7kLn3Y0pUhxm40knbORO3h60I4TExnDA1MvbkK7o6WqkWDcRpczhSkE9tfS2FYhaGIh3lhlzq9GryokM54O2Gyt+fdrWG8ogIdDGRVB83IB4TEbY5OeIf4M3XQ2WkREmZs17AfKGZ1qYaRq1mzlTpuFCbjdXSRIfRQHtyBvVJGlqSUmn8NAl9cDAxPp74eLghONgjSDxc2eLgQH1dBQ2mk4z1t2MbMdNcnceVpgK6TMVYTuv4dbiJYmUEx6KV3CytpFurJztYgVYmI0bqhdTdha3OHyLYuXohODkjFqcxM2Skv6GEYn0SinA5CvkujKpwjkQG0pFzkGSFNyn7ZbSoM6mPSyA9SEb+/v3E7vJF6iZB2O6IEBLkh4/fbvraEtj8wokbR4NIycxEn6ejp72amf7z9BrSuFqoYrgiGVXYHsqUsWQHyQlxdackIppEuRxfz//LdggWQyizvQlsPNSwarVn5lIEy4vzLC8tsrz0isX5F/wy9ZA7F5spiNtHtGI3lpJURhtE+mtELGIuaUGBeLjsQNi6BWHwlJqVkSjWR6X80G7HgMEJ272brG9ssPLbMr+v/MHCy9cszM/TbW7F19eF8D3u1KYfovdENn2VaTRowgj228kH250QbNYqHneqGKt055peQnuaJ5PX65gc62e89xzjVxq4011OcW4CKYnRBPpJ2O3piLfEHk9ne/ZJJSj3ehEg9cBt507+A8OtM7aUdbu2AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/a08c97f5281d31661ba3946fbc3b1ec0/a331c/2026-03-16-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>You may already know that <a href="/en/the_end_of_coding_wrong_question/">I’m a GenAI sceptic</a>. And a general sceptic.</p> <p>Do you know that scepticism comes from the Greek <em>σκέπτομαι</em> (<em>skeptomai</em>), 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.</p> <p>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 <a href="https://www.architecture-weekly.com/p/requiem-for-a-10x-engineer-dream">10x Dev</a>, but I’m finding more ways to get help from it.</p> <p><strong>One of the ways that helped me is something I call <em>“Interactive Rubber-Ducking”</em>.</strong></p> <p>Initially, I called it just <a href="https://www.architecture-weekly.com/p/start-alone-then-together-why-software">brainstorming</a>, but that wouldn’t be precise, as I’m not using it to brainstorm ideas, more to challenge and clarify them.</p> <p>Most of the code I write nowadays is done in <a href="https://github.com/oskardudycz/">my OSS projects</a>. I’m grateful to have a <a href="https://discord.gg/fTpqUTMmVa">great community</a> 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 <a href="https://www.architecture-weekly.com/p/workflow-engine-design-proposal-tell">RFC process</a> 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 <a href="/en/fifteen_tips_on_how_to_run_meetings_effectively/">effective discussion</a>. 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.</p> <p>GenAI tools are not great sparing partners. They’re <em>Yes men</em>. 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 <a href="https://www.merriam-webster.com/dictionary/sycophant">sycophant</a>. You can ask numerous MUSTS with capital letters and bolded <strong>NEVER</strong> here and there, and it can help, but it won’t fully beat the way they were trained.</p> <p>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.</p> <p>Ok, so why would we take those two blind “people” and try to make them help each other?</p> <p><strong>That’s kinda what “Interactive Rubber-Ducking” is.</strong> It takes a blind human with an idea, and another blind not-so-human asking questions. It starts with such a prompt:</p> <blockquote> <p>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.</p> <p>Once we are done, save the spec as spec.md</p> <p>Before asking another question, store the previous one with the answer in qa.md. Write literally the question and answer, not just a summary.</p> <p>Here’s the idea:</p> </blockquote> <p>Don’t treat it as <em>“one magical prompt that will change your life”</em>. Most important is why we’re doing it, what happens next, and who’s actually doing the work. Spoiler alert: it’s not LLM.</p> <p>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.</p> <p>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 <a href="https://context7.com/">Context 7</a> to check documentation and APIs for popular libraries. Then it’s getting more into brainstorming sometimes, than rubber-ducking, but that’s fine.</p> <p>As a result, we’ll get two artefacts:</p> <ul> <li><strong>qa.md</strong> - with the log of the back-and-forth discussion,</li> <li><strong>spec.md</strong> - in theory spec built by LLM, but imho it’s more of a concise summary.</li> </ul> <p>It may look like Specification-Driven Design, but it’s not.</p> <p>My goal for this exercise is not to get an actionable specification.</p> <p><strong>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.</strong></p> <p>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.</p> <p>It also helps to see how our design may be seen by others, especially such mediocre thinkers as LLMs.</p> <p>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&#x26;A until I’m satisfied.</p> <p>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.</p> <p>The example? Why not.</p> <p><strong>I recently did such an exercise, trying to narrow down how to introduce the <em>Second-level cache</em> to <a href="https://github.com/event-driven-io/pongo">Pongo</a> and <a href="https://github.com/event-driven-io/emmett">Emmett</a>.</strong> What’s <em>Second-level cache</em>? A Second-level cache is a local store of data managed by the persistence provider to improve application performance.</p> <p><strong>Why do I want to introduce it?</strong> Because I got an <a href="https://github.com/event-driven-io/emmett/issues/322">issue from the user</a> that <a href="/en/rebuilding_event_driven_read_models/">rebuilding projections</a> with a lot events can take too long. One of the reasons is that applying an event on a projection takes:</p> <ul> <li>loading the current state,</li> <li>updating it,</li> <li>storing the result.</li> </ul> <p>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:</p> <ul> <li>group events by the target documents,</li> <li>load all of them in one operation by finding all documents within an array of ids (taken from events),</li> <li>caching them,</li> <li>applying events in memory,</li> <li>storing updated documents in one operation.</li> </ul> <p>Then we’ll get at worst 102 operations for 100 events and 1002 for 1000 events, so a linear increase.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p>Tell me your thoughts, if that matches your way, if you haven’t done it yet, try it and tell me how it went!</p> <hr> <h2 id="interactive-rubber-ducking-example" style="position:relative;"><a href="#interactive-rubber-ducking-example" aria-label="interactive rubber ducking example permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Interactive Rubber Ducking example</h2> <h3 id="my-idea" style="position:relative;"><a href="#my-idea" aria-label="my idea permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>My idea</h3> <p>I’d like to provide 2nd-level cache to Pongo. I want to either use keyv (<a href="https://www.npmjs.com/package/keyv">https://www.npmjs.com/package/keyv</a>) or lru-cache (<a href="https://www.npmjs.com/package/lru-cache">https://www.npmjs.com/package/lru-cache</a>) packages.</p> <p>For now, I’d like to do it by id.</p> <p>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.</p> <p>Cache should be set up either on the pongo collection, pongo db or pongoClient.</p> <p>Optimistic Concurrency errors should invalidate cache (as that means someone else updated the record in db, e.g. through a different node).</p> <p>InsertMany and insertOne should put it into the cache.</p> <p>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.</p> <p>Thoughts?</p> <p>Which defaults should I set? (Of course, allowing users to override it).</p> <p>Check the related code I pasted. I want to keep it aligned and as non-invasive as possible.</p> <h3 id="q1-cache-library-choice--lru-cache-keyv-or-custom-interface" style="position:relative;"><a href="#q1-cache-library-choice--lru-cache-keyv-or-custom-interface" aria-label="q1 cache library choice lru cache keyv or custom interface permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q1: Cache library choice — lru-cache, keyv, or custom interface?</h3> <p><strong>Question:</strong> You mentioned both <code class="language-text">keyv</code> and <code class="language-text">lru-cache</code>. They pull in different directions. Which trade-off do you prefer?</p> <ul> <li>A) Start with <code class="language-text">lru-cache</code>, optionally let users swap adapters later.</li> <li>B) Start with <code class="language-text">keyv</code> so cross-node sharing works out of the box.</li> <li>C) Define a thin cache interface (<code class="language-text">get</code>/<code class="language-text">set</code>/<code class="language-text">delete</code>/<code class="language-text">clear</code>) and ship <code class="language-text">lru-cache</code> as the default adapter.</li> </ul> <p><strong>Answer:</strong> 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.</p> <h3 id="q2-where-should-the-cache-live-in-pongos-hierarchy" style="position:relative;"><a href="#q2-where-should-the-cache-live-in-pongos-hierarchy" aria-label="q2 where should the cache live in pongos hierarchy permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q2: Where should the cache live in Pongo’s hierarchy?</h3> <p><strong>Question:</strong> Three attachment points: A) PongoClient level (shared across all), B) PongoDb level (per database), C) PongoCollection level (per collection). Or configurable at multiple levels?</p> <p><strong>Answer:</strong> Allow defining it at different levels — cascading configuration. Set a default higher up, override per collection if needed.</p> <h3 id="q3-inheritanceoverride-behavior-for-cascading-cache-config" style="position:relative;"><a href="#q3-inheritanceoverride-behavior-for-cascading-cache-config" aria-label="q3 inheritanceoverride behavior for cascading cache config permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q3: Inheritance/override behavior for cascading cache config?</h3> <p><strong>Question:</strong> For the cascading config — what should the inheritance/override behavior be?</p> <p>Two sub-questions:</p> <ol> <li>Should a lower level be able to <em>disable</em> caching even if a parent enables it?</li> <li>Should cache <em>settings</em> (like max size, TTL) also cascade, or only the cache provider instance itself?</li> </ol> <p><strong>Answer:</strong></p> <ol> <li>Yes. Cache should be an object with type, general params and specific for this type, or <code class="language-text">'disabled'</code>. That’s a proper devex.</li> <li>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.</li> </ol> <p><strong>Claude’s thoughts:</strong> The <code class="language-text">CacheConfig | 'disabled'</code> 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 <code class="language-text">undefined</code>) and “I explicitly want defaults” (reset) — <code class="language-text">undefined</code> vs <code class="language-text">'disabled'</code> vs <code class="language-text">CacheConfig</code> gives a clean three-state: inherit / off / explicit.</p> <h3 id="q4-cache-interface-shape--async-batch-ops-scoping" style="position:relative;"><a href="#q4-cache-interface-shape--async-batch-ops-scoping" aria-label="q4 cache interface shape async batch ops scoping permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q4: Cache interface shape — async, batch ops, scoping?</h3> <p><strong>Question:</strong> Settling the cache interface details:</p> <ol> <li>Async or sync? Making the interface <code class="language-text">Promise</code>-based from the start keeps it pluggable. Sound right?</li> <li>Batch methods (<code class="language-text">getMany</code>, <code class="language-text">setMany</code>, <code class="language-text">deleteMany</code>) — should the cache interface support batch ops natively, or loop internally?</li> <li>Should <code class="language-text">clear()</code> be scoped per collection, or always clear the entire cache?</li> </ol> <p><strong>Answer:</strong></p> <ol> <li>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.</li> <li>Having them (even as syntactic sugar) would be a good devex.</li> <li>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.</li> </ol> <p><strong>Claude’s thoughts:</strong> <code class="language-text">MaybePromise&lt;T></code> (i.e. <code class="language-text">T | PromiseLike&lt;T></code>) works — <code class="language-text">await</code> 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. <code class="language-text">clear()</code> 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 <code class="language-text">max</code> (max entries) and <code class="language-text">ttl</code> (TTL in ms). Start with <code class="language-text">max</code> and <code class="language-text">ttl</code> as the general params.</p> <h3 id="q5-cache-key-strategy" style="position:relative;"><a href="#q5-cache-key-strategy" aria-label="q5 cache key strategy permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q5: Cache key strategy</h3> <p><strong>Question:</strong> Since cache instances might be shared across collections, how to avoid key collisions?</p> <ul> <li>A) Pongo manages key prefixing internally (<code class="language-text">{dbName}:{collectionName}:{id}</code>). The cache interface works with plain string keys, namespacing is Pongo’s concern.</li> <li>B) Key is just the document <code class="language-text">_id</code>. Simpler but only safe with per-collection instances.</li> </ul> <p><strong>Answer:</strong> A, definitely.</p> <h3 id="q6-cache-invalidation-and-consistency-with-optimistic-concurrency" style="position:relative;"><a href="#q6-cache-invalidation-and-consistency-with-optimistic-concurrency" aria-label="q6 cache invalidation and consistency with optimistic concurrency permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q6: Cache invalidation and consistency with optimistic concurrency</h3> <p><strong>Question:</strong> How should cache interact with different operations?</p> <ol> <li>Successful writes → update/evict cache. Clear.</li> <li>Concurrency errors → evict stale entry. Clear.</li> <li>Reads with <code class="language-text">_id</code> filter → cache hit or miss-then-populate. Clear.</li> <li><code class="language-text">handle</code> method — should the read inside <code class="language-text">handle</code> use the cache (risking more concurrency errors on stale data) or always go to DB?</li> </ol> <p><strong>Answer:</strong> Definitely use cache. We could consider disabling cache through handle options.</p> <p><strong>Claude’s thoughts:</strong> Use cache by default — optimistic concurrency is the safety net. If cached version is stale, write fails, we evict, caller can retry. Adding <code class="language-text">skipCache?: boolean</code> to <code class="language-text">HandleOptions</code> is a clean escape hatch for high-contention cases.</p> <h3 id="q7-should-skipcache-be-available-on-read-methods-too" style="position:relative;"><a href="#q7-should-skipcache-be-available-on-read-methods-too" aria-label="q7 should skipcache be available on read methods too permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q7: Should <code class="language-text">skipCache</code> be available on read methods too?</h3> <p><strong>Question:</strong> Should <code class="language-text">skipCache</code> also be available on <code class="language-text">findOne</code> and other read methods for symmetry, or keep it <code class="language-text">handle</code>-only?</p> <p><strong>Answer:</strong> Good point, having <code class="language-text">skipCache</code> on find methods (also <code class="language-text">findMany</code> etc.) would be good.</p> <p><strong>Claude’s thoughts:</strong> Consistent API surface. Add <code class="language-text">skipCache?: boolean</code> on the shared options type (e.g. <code class="language-text">CollectionOperationOptions</code>) so it’s available everywhere without repeating yourself.</p> <h3 id="q8-batch-operations-on-handle--signature-and-handler-shape" style="position:relative;"><a href="#q8-batch-operations-on-handle--signature-and-handler-shape" aria-label="q8 batch operations on handle signature and handler shape permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q8: Batch operations on <code class="language-text">handle</code> — signature and handler shape</h3> <p><strong>Question:</strong> You want <code class="language-text">handle</code> to accept an id or array of ids. Two design choices:</p> <ol> <li>Overloaded signature (<code class="language-text">handle(id: string | string[], ...)</code>) vs separate method (<code class="language-text">handleMany</code>)?</li> <li>Handler shape for batch: A) still receives/returns one document at a time (Pongo batches DB ops internally) or B) receives/returns full array?</li> </ol> <p>Also noted: since PongoDocuments always have <code class="language-text">_version</code>, if someone provides <code class="language-text">expectedVersion</code> we could compare it against the cached version and decide if it’s stale without hitting the DB.</p> <p><strong>Answer:</strong> Option A for handler shape. <code class="language-text">handleMany</code> would be consistent with other APIs, but single id or array (<code class="language-text">string | string[]</code>) is clean enough.</p> <p><strong>Claude’s thoughts:</strong> Version comparison in cache is a cheap short-circuit — if cached <code class="language-text">_version</code> doesn’t match <code class="language-text">expectedVersion</code>, we know it’s stale without a round trip. On signature: TypeScript overloads can narrow the return type (<code class="language-text">string</code> → single result, <code class="language-text">string[]</code> → array). <code class="language-text">handle</code> is a higher-level orchestration method, not direct CRUD, so a union input feels natural. Go with <code class="language-text">handle(id: string | string[], handler, options?)</code>.</p> <h3 id="q9-defaults--max-entries-ttl-enabled-by-default" style="position:relative;"><a href="#q9-defaults--max-entries-ttl-enabled-by-default" aria-label="q9 defaults max entries ttl enabled by default permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q9: Defaults — max entries, TTL, enabled by default?</h3> <p><strong>Question:</strong> What defaults to set? Proposed: <code class="language-text">max: 1000</code>, <code class="language-text">ttl: 60_000</code> (1 min), and should cache be enabled by default or opt-in?</p> <p><strong>Answer:</strong> Stick to recommended lru-cache defaults for now. Enable cache by default.</p> <p><strong>Claude’s thoughts:</strong> 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 <code class="language-text">'disabled'</code>. <code class="language-text">lru-cache</code> requires <code class="language-text">max</code> to be set (no default) and has no default TTL. Suggested: <code class="language-text">max: 1000</code>, no TTL (rely on LRU eviction + invalidation on writes/concurrency errors) — most lru-cache-aligned.</p> <h3 id="q10-ttl-or-no-ttl-as-staleness-backstop" style="position:relative;"><a href="#q10-ttl-or-no-ttl-as-staleness-backstop" aria-label="q10 ttl or no ttl as staleness backstop permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q10: TTL or no TTL as staleness backstop?</h3> <p><strong>Question:</strong> 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:</p> <ul> <li>A) No TTL, accept the trade-off — OC catches it on next write.</li> <li>B) Set a conservative default TTL (e.g., 5 minutes) as a staleness bound.</li> </ul> <p><strong>Answer:</strong> Fine with using TTL and sometimes hitting OC issue. OC assumes rare conflicts.</p> <h3 id="q11-where-does-cache-integration-hook-into-existing-code" style="position:relative;"><a href="#q11-where-does-cache-integration-hook-into-existing-code" aria-label="q11 where does cache integration hook into existing code permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q11: Where does cache integration hook into existing code?</h3> <p><strong>Question:</strong> Two approaches for minimal invasiveness:</p> <ol> <li>Wrap at the <code class="language-text">pongoCollection</code> level — intercept operations inside the factory function, driver/SQL layers untouched.</li> <li>Decorator/wrapper pattern — <code class="language-text">cachedPongoCollection</code> wraps <code class="language-text">PongoCollection</code>, adds cache logic around delegation.</li> </ol> <p><strong>Answer:</strong> Leaning more to option 1. Thinks it’ll be easier to make observability precise eventually.</p> <p><strong>Claude’s assessment:</strong> Agreed with option 1. Cache decisions depend on internal state (whether filter targets <code class="language-text">_id</code>, document <code class="language-text">_version</code>, 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.</p> <h3 id="q12-cache-interaction-with-transactions" style="position:relative;"><a href="#q12-cache-interaction-with-transactions" aria-label="q12 cache interaction with transactions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q12: Cache interaction with transactions</h3> <p><strong>Question:</strong> How should cache interact with transactions?</p> <ol> <li>Should writes within a transaction update the cache immediately, or only after commit?</li> <li>Should reads within a transaction check the cache?</li> </ol> <p><strong>Answer:</strong></p> <ol> <li>After commit.</li> <li>We should use cache, but allow skipping it on transaction or session options. We can also allow setting cache settings per session.</li> </ol> <p><strong>Claude’s thoughts:</strong> Reads within a transaction benefiting from cache is more aggressive but logical — most transactions are short-lived. The narrow case: <code class="language-text">insertOne</code> then <code class="language-text">findOne</code> same <code class="language-text">_id</code> 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., <code class="language-text">startSession({ cache: 'disabled' })</code> for batch migrations). Proposed cascade priority: client → db → collection → session → per-operation <code class="language-text">skipCache</code>.</p> <h3 id="q13-cascade-priority--does-session-override-collection" style="position:relative;"><a href="#q13-cascade-priority--does-session-override-collection" aria-label="q13 cascade priority does session override collection permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q13: Cascade priority — does session override collection?</h3> <p><strong>Question:</strong> Does the cascade priority <strong>client → db → collection → session → per-operation <code class="language-text">skipCache</code></strong> 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?</p> <p><strong>Answer:</strong> 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.</p> <p><strong>Claude’s thoughts:</strong> 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 <code class="language-text">skipCache</code> on every call. Final cascade: client → db → collection → session → per-operation.</p> <h3 id="q14-cache-provider-error-handling" style="position:relative;"><a href="#q14-cache-provider-error-handling" aria-label="q14 cache provider error handling permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q14: Cache provider error handling</h3> <p><strong>Question:</strong> If the cache provider throws (e.g., Redis connection drops), should Pongo:</p> <ul> <li>A) Treat it as a cache miss — fall through to DB silently, maybe log a warning. Cache failures never break the application.</li> <li>B) Propagate the error — let the caller deal with it.</li> </ul> <p><strong>Answer:</strong> A — yes, when we add observability to Pongo, we need to make it also a metric and trace event/attribute/log entry.</p> <p><strong>Claude’s thoughts:</strong> Cache is an optimization, not a correctness requirement. Silent degradation now, observable degradation when the observability layer lands.</p> <h3 id="q15-cache-warming-eventshooks-and-delete-operations" style="position:relative;"><a href="#q15-cache-warming-eventshooks-and-delete-operations" aria-label="q15 cache warming eventshooks and delete operations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Q15: Cache warming, events/hooks, and delete operations</h3> <p><strong>Question:</strong> Three remaining areas:</p> <ol> <li>Cache warming — should Pongo support pre-populating the cache on startup?</li> <li>Cache events/hooks — should users be able to listen to cache hits/misses/evictions?</li> <li>Delete operations — <code class="language-text">deleteOne</code>/<code class="language-text">deleteMany</code> should evict from cache. Anything non-obvious there?</li> </ol> <p><strong>Answer:</strong></p> <ol> <li>For now keep it as a future concept, or define pluggable strategy for users.</li> <li>You can add the basic hook to the Pongo Cache wrapper. Shouldn’t be that hard to just call some function.</li> <li>Yes, they should evict if they can, we can delete (for now) when <code class="language-text">deleteMany</code> deletes by ids.</li> </ol> <p><strong>Claude’s thoughts:</strong> 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 <code class="language-text">deleteMany</code> doesn’t evict (no way to know which ids were affected without querying first).</p> <hr> <h2 id="interactive-rubber-ducking-result" style="position:relative;"><a href="#interactive-rubber-ducking-result" aria-label="interactive rubber ducking result permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Interactive Rubber Ducking result</h2> <h3 id="pongo-2nd-level-cache--specification" style="position:relative;"><a href="#pongo-2nd-level-cache--specification" aria-label="pongo 2nd level cache specification permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Pongo 2nd Level Cache — Specification</h3> <h3 id="overview" style="position:relative;"><a href="#overview" aria-label="overview permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Overview</h3> <p>Add a document-level (by <code class="language-text">_id</code>) 2nd level cache to Pongo with a pluggable provider interface. Ships with an in-memory provider backed by <code class="language-text">lru-cache</code>. Enabled by default. Designed to be minimally invasive to existing code.</p> <h3 id="cache-interface" style="position:relative;"><a href="#cache-interface" aria-label="cache interface permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cache Interface</h3> <h4 id="pongocacheprovidert" style="position:relative;"><a href="#pongocacheprovidert" aria-label="pongocacheprovidert permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><code class="language-text">PongoCacheProvider&lt;T></code></h4> <p>A thin, Pongo-owned interface. No coupling to any third-party API.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">MaybePromise<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span> <span class="token operator">|</span> PromiseLike<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">PongoCacheProvider</span> <span class="token punctuation">{</span> <span class="token function">get</span><span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> MaybePromise<span class="token operator">&lt;</span>PongoDocument <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">set</span><span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> value<span class="token operator">:</span> PongoDocument<span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">{</span> ttl<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">:</span> MaybePromise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">delete</span><span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> MaybePromise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">getMany</span><span class="token punctuation">(</span>keys<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> MaybePromise<span class="token operator">&lt;</span><span class="token punctuation">(</span>PongoDocument <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">setMany</span><span class="token punctuation">(</span>entries<span class="token operator">:</span> <span class="token punctuation">{</span> key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> value<span class="token operator">:</span> PongoDocument<span class="token punctuation">;</span> ttl<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> MaybePromise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">deleteMany</span><span class="token punctuation">(</span>keys<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> MaybePromise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> MaybePromise<span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <ul> <li><code class="language-text">MaybePromise</code> return types: sync providers (in-memory) return values directly, async providers (Redis) return Promises. <code class="language-text">await</code> handles both transparently.</li> <li>Batch methods (<code class="language-text">getMany</code>, <code class="language-text">setMany</code>, <code class="language-text">deleteMany</code>) are first-class. Default in-memory implementation may loop internally, but the interface allows optimized batch ops for external providers.</li> <li><code class="language-text">clear()</code> 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.</li> </ul> <h4 id="cache-key-strategy" style="position:relative;"><a href="#cache-key-strategy" aria-label="cache key strategy permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cache key strategy</h4> <p>Pongo manages key prefixing internally: <code class="language-text">{dbName}:{collectionName}:{documentId}</code>.</p> <p>The cache provider works with plain string keys — namespacing is Pongo’s concern, not the provider’s.</p> <h4 id="event-hooks" style="position:relative;"><a href="#event-hooks" aria-label="event hooks permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Event hooks</h4> <p>The Pongo cache wrapper supports basic callbacks:</p> <ul> <li><code class="language-text">onHit?(key: string): void</code></li> <li><code class="language-text">onMiss?(key: string): void</code></li> <li><code class="language-text">onEvict?(key: string): void</code></li> <li><code class="language-text">onError?(error: unknown, operation: string): void</code></li> </ul> <p>These are optional and intended for debugging and future observability integration.</p> <h3 id="configuration" style="position:relative;"><a href="#configuration" aria-label="configuration permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Configuration</h3> <h4 id="cacheconfig" style="position:relative;"><a href="#cacheconfig" aria-label="cacheconfig permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><code class="language-text">CacheConfig</code></h4> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">CacheConfig</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token comment">// e.g., 'in-memory', 'redis', etc.</span> max<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token comment">// max entries (general param, cascades)</span> ttl<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token comment">// TTL in ms (general param, cascades)</span> <span class="token comment">// type-specific options live here too, keyed by type</span> <span class="token punctuation">[</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">]</span><span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token string">'disabled'</span><span class="token punctuation">;</span></code></pre></div> <p>Three states:</p> <ul> <li><code class="language-text">undefined</code> — inherit from parent level</li> <li><code class="language-text">'disabled'</code> — explicitly turn off caching at this level</li> <li><code class="language-text">CacheConfig</code> object — explicit configuration</li> </ul> <h4 id="cascading-configuration" style="position:relative;"><a href="#cascading-configuration" aria-label="cascading configuration permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cascading configuration</h4> <p>Cache config can be set at multiple levels. Each level inherits from its parent unless explicitly overridden:</p> <p><strong>client → db → collection → session → per-operation</strong></p> <p>Inheritance rules:</p> <ul> <li>General params (<code class="language-text">max</code>, <code class="language-text">ttl</code>) cascade down.</li> <li>If a lower level switches <code class="language-text">type</code>, type-specific params reset to defaults (not inherited from parent).</li> <li>Session overrides collection — session is a logical grouping of operations, natural place to override runtime behavior (e.g., disable cache for a bulk import).</li> <li>Per-operation <code class="language-text">skipCache?: boolean</code> is the most granular escape hatch.</li> </ul> <h4 id="passing-a-cache-instance" style="position:relative;"><a href="#passing-a-cache-instance" aria-label="passing a cache instance permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Passing a cache instance</h4> <p>Users can provide either:</p> <ul> <li><strong>Settings</strong> — Pongo creates and manages the cache provider.</li> <li><strong>A pre-built cache provider instance</strong> — Pongo uses it directly.</li> </ul> <p>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.</p> <h4 id="defaults" style="position:relative;"><a href="#defaults" aria-label="defaults permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Defaults</h4> <ul> <li><strong>Enabled by default</strong></li> <li><code class="language-text">max</code>: follow <code class="language-text">lru-cache</code> recommended defaults (1000)</li> <li><code class="language-text">ttl</code>: follow <code class="language-text">lru-cache</code> recommended defaults</li> <li>Default provider: in-memory (<code class="language-text">lru-cache</code>)</li> </ul> <h3 id="integration-points" style="position:relative;"><a href="#integration-points" aria-label="integration points permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Integration points</h3> <h4 id="where-pongocollection-factory-function" style="position:relative;"><a href="#where-pongocollection-factory-function" aria-label="where pongocollection factory function permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Where: <code class="language-text">pongoCollection</code> factory function</h4> <p>Cache logic is added directly inside <code class="language-text">pongoCollection</code>, not as an external decorator/wrapper. This gives cache operations access to internal state (filter inspection, <code class="language-text">_version</code>, write outcomes) and keeps observability precise.</p> <h4 id="read-operations" style="position:relative;"><a href="#read-operations" aria-label="read operations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Read operations</h4> <p><strong><code class="language-text">findOne</code>:</strong></p> <ul> <li>If the filter targets <code class="language-text">_id</code>, check cache first.</li> <li>Cache hit → return cached document.</li> <li>Cache miss → query DB, populate cache, return.</li> <li><code class="language-text">skipCache?: boolean</code> option available.</li> </ul> <p><strong><code class="language-text">findMany</code> / other query methods:</strong></p> <ul> <li>If the filter is a list of <code class="language-text">_id</code> values, check cache for each.</li> <li>Return cached hits, query DB for misses, populate cache with DB results.</li> <li>Non-<code class="language-text">_id</code> filters bypass cache entirely (cache is by-id only).</li> </ul> <h4 id="write-operations" style="position:relative;"><a href="#write-operations" aria-label="write operations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Write operations</h4> <p><strong><code class="language-text">insertOne</code>:</strong></p> <ul> <li>After successful insert, put the document into cache.</li> </ul> <p><strong><code class="language-text">insertMany</code>:</strong></p> <ul> <li>After successful insert, put all documents into cache.</li> </ul> <p><strong><code class="language-text">updateOne</code> / <code class="language-text">updateMany</code> / <code class="language-text">replaceOne</code>:</strong></p> <ul> <li>After successful write, update the cache entry with the new document state.</li> </ul> <p><strong><code class="language-text">deleteOne</code>:</strong></p> <ul> <li>After successful delete, evict from cache.</li> </ul> <p><strong><code class="language-text">deleteMany</code>:</strong></p> <ul> <li>If deleting by ids, evict those ids from cache.</li> <li>Filter-based <code class="language-text">deleteMany</code> does not evict (no way to know affected ids without extra query). Future improvement possible.</li> </ul> <h3 id="optimistic-concurrency" style="position:relative;"><a href="#optimistic-concurrency" aria-label="optimistic concurrency permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Optimistic concurrency</h3> <ul> <li>On concurrency error (version mismatch), <strong>evict the stale entry</strong> 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.</li> <li>If a caller provides <code class="language-text">expectedVersion</code> and the cached document has <code class="language-text">_version</code>, compare them in memory. If they don’t match, we know it’s stale without hitting the DB — cheap short-circuit.</li> </ul> <h4 id="handle-method" style="position:relative;"><a href="#handle-method" aria-label="handle method permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><code class="language-text">handle</code> method</h4> <ul> <li>Accepts <code class="language-text">id: string | string[]</code> (overloaded: string returns single result, string[] returns array).</li> <li>Read phase uses cache by default.</li> <li>Write phase updates/evicts cache based on outcome.</li> <li><code class="language-text">skipCache?: boolean</code> available in handle options.</li> <li>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.</li> </ul> <h4 id="transactions" style="position:relative;"><a href="#transactions" aria-label="transactions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Transactions</h4> <ul> <li><strong>Writes update cache only after commit.</strong> Not during the transaction — uncommitted data should not enter the cache.</li> <li><strong>Reads within a transaction check cache</strong> 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.</li> <li>Cache can be disabled per-session: <code class="language-text">startSession({ cache: 'disabled' })</code>.</li> <li>Per-session cache settings follow the cascade (session overrides collection).</li> </ul> <h3 id="error-handling" style="position:relative;"><a href="#error-handling" aria-label="error handling permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Error handling</h3> <p>Cache provider errors are <strong>swallowed</strong> — treated as cache misses. The application never fails because of a cache failure.</p> <ul> <li>Cache is an optimization, not a correctness requirement.</li> <li>Errors trigger <code class="language-text">onError</code> hook for debugging.</li> <li>Future: errors will become metrics and trace events when Pongo’s observability layer lands.</li> </ul> <h3 id="future-considerations-out-of-scope-for-v1" style="position:relative;"><a href="#future-considerations-out-of-scope-for-v1" aria-label="future considerations out of scope for v1 permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Future considerations (out of scope for v1)</h3> <ul> <li><strong>Cache warming</strong>: pluggable strategy for pre-populating cache on startup. Interface hook defined but no default implementation shipped.</li> <li><strong>Query-result caching</strong>: caching results of non-<code class="language-text">_id</code> queries. Complex invalidation — deferred.</li> <li><strong>Cross-node cache sharing</strong>: Redis/Memcached providers. The interface supports it; no provider shipped in v1.</li> <li><strong>Filter-based <code class="language-text">deleteMany</code> eviction</strong>: would require querying affected ids before delete.</li> <li><strong>Observability integration</strong>: metrics (hit ratio, latency saved, eviction counts), trace attributes, log entries for cache operations.</li> </ul> <h3 id="implementation-approach" style="position:relative;"><a href="#implementation-approach" aria-label="implementation approach permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Implementation approach</h3> <ol> <li>Define <code class="language-text">PongoCacheProvider</code> interface and <code class="language-text">CacheConfig</code> types.</li> <li>Implement in-memory provider using <code class="language-text">lru-cache</code>.</li> <li>Build Pongo cache wrapper (key prefixing, event hooks, error swallowing).</li> <li>Wire cascading config resolution (client → db → collection → session → per-op).</li> <li>Integrate into <code class="language-text">pongoCollection</code>: intercept <code class="language-text">_id</code>-based reads, update cache on writes, evict on deletes and concurrency errors.</li> <li>Extend <code class="language-text">handle</code> to accept <code class="language-text">string | string[]</code> with batch support.</li> <li>Add <code class="language-text">skipCache</code> to operation options.</li> <li>Tests: unit tests for cache provider, integration tests for cache + collection operations, e2e tests for concurrency scenarios.</li> </ol> <hr> <p>Cheers!</p> <p>Oskar</p> <p><strong>p.s.</strong> and yes <a href="https://gemini.google/pl/overview/image-generation/">Nano Banana</a> also did pretty well generating the cover of this post!</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[The End of Coding? Wrong Question]]>https://event-driven.io/en/the_end_of_coding_wrong_question/https://event-driven.io/en/the_end_of_coding_wrong_question/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8dda12a6f19358ea86de7c87dfa9d46c/a331c/2026-03-09-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADT0lEQVQ4y0WR329adRiHz+ra0vGrha6lHOAczjnA4cAB1kKBlha6QqE/gdKOru3sXLOZGl3Msmy2Ro0XZs7NeOGiRm+MxugSbzXGC5clJsYLY4x3Xpj4B/g3PIZjjRdP3nzfi+f9vp9XCBRS+PIpfLkEnlQMMRNhbjZKflpBSyu4VYlzUgBHOMQ5KcigJGILB7CFg9hViSFVwh2PMJzU8aR0BLmSIzSfxVdMoc3neHTvmKdff8rjD97maK9EtZ5mRAvhM02WtjtU2hvMtzeYa62RqS5Q6bQ5nzJwxTVGMwZCslVFXy0TXMhz99YVfnn8kCefP+TJFw+42p0jGBrG7fMwauhc3GxRXG2wur/L9vVrlJprNHZ3KKyvIGYzjJg6wsLhZfL7LdzhAPNZlepMkhcP1jl+vorf70QQBM4O2ZhIJaltdcjVa9S6W5Q3m9S62xQ3VlnsbhEvl3AaUYSDV09YvXlErVXnlcMmijRKNq3SaZgMDfXjsPVbwjEzwdzGOrNrK5TbTeyaxBlxAlcsypAq49IjeM04wr2PP+POe4949rDDzUszPLjV5PWjZUpZiWlTZKsUZWDYjf9C2spvsbPJ5aMbVDotCmvLeJM6w0bMYsTQEb7/6Xe+/PYpeztN2sUw++0L5Cb9qNp5ypMyKVWk3+9j3DSYrtfILFbILlWtmrlYwWNEcUYUXDHNQvjjr7/5+bc/uXP7BRoFkWw2SDw5QUj2ctb2DH1eD+6YhkORGAj6GQyJDIREBuUAtpCIM/qfTMWlawjf/fgrX33zA8f336Wy2UDSvEjyMIrkRE5FGIkqlsyphnFH1f+JqbgjCu6e8JReT3jj/Q+5ff8dDl47YX5vi8VqgqWSypXrXerP7eLVw/inEtZqDkXGrkg4tLC1pqN3jEj4X04HCzt3X6ZxYx8ln8avyxSXCnSvdXnrk48wKzOMGiqRco54dQa1PM34ZAK3rlk/7g1w9uRaT6jiVBUEo5pnLCEhDPYRmdLxxQKUGg1eevOEES3ImKEiF9KWNFGfJblcYtyMYJdDOMIyTkXGoZ5KYxEElzSKU/RwZqCPwsoc8aKJkU2zfvUS9pCPsYRKcMpAKqSQ8qb1tgfGsMtB7KeyXgQOTbFi+AegQqhK0SjPpgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/8dda12a6f19358ea86de7c87dfa9d46c/a331c/2026-03-09-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Be careful what you wish for, because your wish may come true.</strong></p> <p>What LLMs revealed is how many people in our industry don’t like to code.</p> <p>It’s intriguing that now they claim and showcase what they “built with Claude”, whereas usually that means they generated a PoC.</p> <p>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?</p> <p><strong>I think that most people look at the forest and see trees.</strong> 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 <a href="https://web.archive.org/web/20200217080706/http://blog.codinghorror.com/new-programming-jargon/">stringly-typed</a> development with chat or markdown.</p> <p>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.</p> <p>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).</p> <p><strong>I think that we’ll still be coding, but with some other layer, as LLMs are good with structured input, like programming languages.</strong> 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?</p> <p>Still, I don’t see those discussions.</p> <p>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.</p> <p><strong>So, will we code or not?</strong></p> <p>If the answer is yes, let’s talk about what’s next, then let’s discuss how and when.</p> <p>If the answer is not, then let’s talk about how our job will change, ask if it’s still engineering, etc.</p> <p>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.</p> <p>Noone outside our industry cares how we code unless it changes the cost, quality, or delivery time.</p> <p>Let’s discuss the impact that matters, rather than just the amount of code we produce (or not).</p> <p>Now, guess who I am quoting:</p> <blockquote> <p>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.</p> <p>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.</p> <p>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.</p> <p>You say to yourself and your friends, “There has to be a better way”!</p> <p><strong>The Better Way is Here Now</strong></p> <p>Now there is a better way—it’s our new model. Imagine, if you will, this development world…</p> <ul> <li>It’s still dead simple.</li> <li>Your development cycle is much faster.</li> <li>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.</li> <li>Your applications are adaptable to changing environments.</li> <li>Your end users can trust that your applications are secure, and you can use protection against viruses and tampering through security scans.</li> </ul> <p>You don’t need to dream about these features. They’re here now.</p> </blockquote> <p>And also this from another source:</p> <blockquote> <p>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.</p> <p>Nowadays, they tend to choose LLMs.</p> <p>Now, don’t get me wrong: there’s nothing wrong with LLN as an implementation tool.</p> <p>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.</p> <p>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.</p> </blockquote> <p>Well, I cheated you, but only a bit. I changed “Java” to “LLM ” and cut some phrases.</p> <p>The first one is from <a href="https://www.stroustrup.com/1995_Java_whitepaper.pdf">“The Java Language Environment”</a> by Sun Microsystems, introducing Java in 1995.</p> <p>The second one was from Joel Spolsky’s <a href="https://www.joelonsoftware.com/2005/12/29/the-perils-of-javaschools-2/blog">“The Perils of JavaSchools”</a> article, written in 2001.</p> <p>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.</p> <p>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.</p> <p><strong>The goal of abstraction is not to gatekeep but to allow us to reduce cognitive load.</strong> 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.</p> <p>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.</p> <p>Will Claude-native do the same?</p> <p>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:</p> <blockquote> <p>Things are the way they are because they got that way</p> </blockquote> <p>And now the question is whether we’re fine with the way we’re doing stuff, and where we’re getting to.</p> <p>Personally, I don’t think that stringly-typed markdown or chat-based design will be the thing in the future.</p> <p>Knowing how skilled we were always with:</p> <ul> <li>breaking down tasks into smaller chunks,</li> <li>writing precisely what we had in mind,</li> <li>thinking before doing,</li> <li>waging tradeoffs,</li> </ul> <p>I’m optimistic about the Spec-Driven Design idea. It’s going to be great.</p> <p>Not.</p> <p>If we want to call ourselves engineers, we need to put more structure and determinism.</p> <p>I agree that reviewing all code generated by the GenAI is not sustainable.</p> <p>But I also don’t think that generating tons of code is, in general, sustainable.</p> <p>With the current state of the art we have, sure, that’s some solution to just generate based on the tools we have.</p> <p>But let’s start to think what’s next.</p> <p>Simon Wardley <a href="https://www.linkedin.com/feed/update/urn:li:activity:7426977059677077504/">brought an interesting point</a>:</p> <blockquote> <p>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.</p> </blockquote> <p>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.</p> <p>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 <em>“proper design”</em>? Yes, code style doesn’t matter as long as it works as expected. But…</p> <p>But code eventually matters, as that’s the source of truth for what’s on production. As Alberto Brandolini said:</p> <blockquote> <p>It’s developers’ (mis)understanding, not domain experts’ knowledge, that gets released in production.</p> </blockquote> <p>Now, it’s the developers’ and LLMs’ misunderstandings that are deployed to production, not the expert’s knowledge. Neither the markdown spec.</p> <p>And coding is just one danger.</p> <p><strong>Outsourcing thinking is an even more dangerous path, as:</strong></p> <ul> <li>If LLMs are doing everything, then what are humans for? Aren’t we cutting the branch on which we’re sitting?</li> <li>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.</li> <li>Just like we’re losing our coding skills by not doing them, we’re losing design skills by not practising them.</li> </ul> <p>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.</p> <p>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.</p> <p>I’d like to think about how to reshape our SDLC process and make it sustainable.</p> <p>I’d like us to think about what tools we need, and how to change what we have.</p> <p>If we won’t finally start to do it, then things will be the way they are because they got that way.</p> <p>And it might not be what we wished for.</p> <p>Cheers</p> <p>Oskar</p> <p><strong>p.s.</strong> 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!</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Parse, Don't Guess]]>https://event-driven.io/en/parse_dont_guess/https://event-driven.io/en/parse_dont_guess/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/9612dd92364aaa25830c836976efdda9/8537d/2026-03-03-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 52.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByUlEQVQoz22R227UMBRF8/9/wRNUFVSoqKCKm1CfKsSldJg2mdwmcW52bCeTTHlbyJ6hTKEPS/sc5+ztozi422hm3bBpCyYp2I79o/zaaAbdoqTA9g3O99hcMOkGk95gkiU2D5n6mtlK5kGxHRTW9YOkl4Isj0iykGwdId0Cfu4hgRUJOrtl7ArGbs1QJcymRfuAECESjKrI85B8HSK7gqKMCZOfjLphth3TAT5Qrn5g1iG2SrAixm2tupIku6WuUmy/C4xWC0QZUxQr4nTJ0NdMpmXjfplT0xLoIvKBLsyIGFNGjEr4wTt3697QithjpUDVGV0ZM/Y1o653uicYZImnK7xarwKrhDc7BiX82XBYO1XVfwQuwFSpx9XboWO2LbNp2Oia7b722Jbpj+oGu1/GHhAYF9gVWFmgm5zFt0tWy+/EN1cUyQ3Lq8++d9xefyENrwkXX8mihV/g3r8nMO0a3eaYbo2qUj6+PePVyRFnL4/59O6M16fPOX3xjMuL97xx9ckRx0+fcPHh3Huc/5BANxl/yZl0hW1zdJ0gxWqnZYSqYkyT0deJ/+4e7qF3R9DXKYeoKvGb7voMWSX+Rf35/cxu7l+v4zcTfSvfqTwOvwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/9612dd92364aaa25830c836976efdda9/a331c/2026-03-03-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="/en/cloudflare_d1_transactions_and_tradeoffs/">Last time, I shared with you how sneaky I was on transaction handling.</a>. Today, the opposite: I’ll tell you how I fixed the issue when I tried to be too sneaky. I already told you that <a href="https://www.architecture-weekly.com/p/sneaky-code-bites-back">Sneaky Code Bites Back</a>. The moral? Do as I tell, not how I do.</p> <p>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ść.</p> <p>Exactly those:</p> <ul> <li>string,</li> <li>number,</li> <li>boolean,</li> <li>object,</li> <li>array,</li> <li>and (TADA!) null.</li> </ul> <p>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 <a href="/en/fun_with_json_serialisation/">here or how much fun that brings</a>.</p> <p>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 <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">Alexis King put in her “Parse, don’t validate”</a>.</p> <p>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.</p> <p>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.</p> <p>As you know from my previous articles (e.g. <a href="/en/checkpointing_message_processing/">this one</a>), 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.</p> <p>I’m using those bigint types extensively in internals in <a href="https://github.com/event-driven-io/emmett">Emmett</a> and <a href="https://github.com/event-driven-io/pongo">Pongo</a>. 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).</p> <p>So, for instance, an event payload can look like:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"InvoiceIssued"</span><span class="token punctuation">,</span> <span class="token property">"data"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"invoiceNumber"</span><span class="token operator">:</span> <span class="token string">"123"</span><span class="token punctuation">,</span> <span class="token property">"version"</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token property">"issuer"</span><span class="token operator">:</span> <span class="token string">"John Doe"</span><span class="token punctuation">,</span> <span class="token property">"issuedAt"</span><span class="token operator">:</span> <span class="token string">"2026-02-23T14:07:20Z"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"metadata"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"streamPosition"</span><span class="token operator">:</span> <span class="token string">"3"</span><span class="token punctuation">,</span> <span class="token property">"globalPosition"</span><span class="token operator">:</span> <span class="token string">"928391"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>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).</p> <p>And encoding data is simple: you convert it to a string, call it a day. But how to get it back?</p> <p>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?</p> <p>There are several options. The first one is: encode value.</p> <p>We could store it, for instance, as:</p> <ul> <li>prefixed value: <strong>“_bigint:928391”</strong>. But then you need to find a prefix that will be unique enough not to cause conflicts,</li> <li>nested object, e.g. <strong>{ “_kind”: “bigint”, value: “928391” }</strong>.</li> </ul> <p>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 <a href="https://github.com/event-driven-io/pongo">Pongo</a> I’m allowing the use of PostgreSQL and SQLite as document databases, supporting such queries:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> invoices <span class="token operator">=</span> pongoDb<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>Invoice<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">"invoices"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> invoiceNumber <span class="token operator">=</span> <span class="token number">123n</span><span class="token punctuation">;</span> <span class="token keyword">const</span> invoice <span class="token operator">=</span> <span class="token keyword">await</span> invoices<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> invoiceNumber <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That gets translated into a <a href="https://www.architecture-weekly.com/p/postgresql-jsonb-powerful-storage">fancy JSONB SQL query</a>.</p> <p>Of course, I could work around it by encoding the value, but… But I was lazy!</p> <p>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.</p> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> bigIntReviver<span class="token operator">:</span> <span class="token function-variable function">JSONReviver</span> <span class="token operator">=</span> <span class="token punctuation">(</span>_key<span class="token punctuation">,</span> value<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">typeof</span> value <span class="token operator">===</span> <span class="token string">'string'</span> <span class="token operator">&amp;&amp;</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">^[+-]?\d+n?$</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> value<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Yes, it’s either DNS or Regex. Or both.</p> <p>I <a href="https://www.architecture-weekly.com/p/typescript-migrates-to-go-whats-really">explained in another article</a> that JavaScript runtime doesn’t like where you do CPU-heavy computations.</p> <p>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 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Execution_model">JavaScript event loop to freeze</a>.</p> <p>What’s more, I plugged that automatically into <a href="https://node-postgres.com/">node-postgres driver</a> custom type handling, so each JSONB deserialization goes through it.</p> <p>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.</p> <p>Fortunately, a user, Dawid, benchmarked and noticed CPU freezes. It wasn’t catastrophic, but clearly needed a fix.</p> <h2 id="the-shift" style="position:relative;"><a href="#the-shift" aria-label="the shift permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The Shift</h2> <p><strong>And here I had several options.</strong> 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.</p> <p>Because that’s the actual mistake here, not the Regex. The <em>pg</em> driver has no idea what your schema looks like. It doesn’t know that <em>“928391”</em> is a bigint and <em>“John Doe”</em> is a name. It doesn’t know that <em>“123”</em> is an invoice number (bigint!) and <em>“90210”</em> is a zip code (string!). I asked it to guess, and it guessed wrong, because there is no right guess at that level.</p> <p>Enough is enough. I had been planning to do ther properly for a while, and the performance issue gave me the push I needed.</p> <p><strong><a href="https://wiki.c2.com/?MakeItWorkMakeItRightMakeItFast">Old rule says: “Make it work, make it right, make it pretty”.</a></strong> I had <em>“make it work”</em> covered for a long time. Now it was time for <em>“make it right”</em>.</p> <p>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.</p> <p>In <a href="https://github.com/event-driven-io/Pongo/pull/149">Pongo</a>, I dropped the automatic bigint parsing from the driver entirely. If you want bigint or date parsing, you say so at the client level:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span> driver<span class="token operator">:</span> databaseDriver<span class="token punctuation">,</span> connectionString<span class="token operator">:</span> postgresConnectionString<span class="token punctuation">,</span> serialization<span class="token operator">:</span> <span class="token punctuation">{</span> options<span class="token operator">:</span> <span class="token punctuation">{</span> parseBigInts<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> parseDates<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p><strong>That was the “make it right” part for Pongo.</strong> 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?</p> <p>And that’s where upcasting comes in. Let me start with a simple example in <a href="https://github.com/event-driven-io/Pongo/pull/149">Pongo</a>, then build up.</p> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">UserDocStored</span> <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> createdAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> lastLogin<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">UserDoc</span> <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> createdAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> lastLogin<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>The upcast function does the conversion:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> upcast <span class="token operator">=</span> <span class="token punctuation">(</span>doc<span class="token operator">:</span> UserDocStored<span class="token punctuation">)</span><span class="token operator">:</span> UserDoc <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token operator">:</span> doc<span class="token punctuation">.</span>name<span class="token punctuation">,</span> createdAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>doc<span class="token punctuation">.</span>createdAt<span class="token punctuation">)</span><span class="token punctuation">,</span> lastLogin<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>doc<span class="token punctuation">.</span>lastLogin<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You wire it into the collection, and every read goes through it:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> collection <span class="token operator">=</span> pongoDb<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>UserDoc<span class="token punctuation">,</span> UserDocStored<span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token string">'users'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> schema<span class="token operator">:</span> <span class="token punctuation">{</span> versioning<span class="token operator">:</span> <span class="token punctuation">{</span> upcast <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>What’s in the database:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Alice"</span><span class="token punctuation">,</span> <span class="token property">"createdAt"</span><span class="token operator">:</span> <span class="token string">"2024-01-15T10:30:00.000Z"</span><span class="token punctuation">,</span> ... <span class="token punctuation">}</span></code></pre></div> <p>What you get back:</p> <div class="gatsby-highlight" data-language="js"><pre class="language-js"><code class="language-js"><span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">'Alice'</span><span class="token punctuation">,</span> <span class="token literal-property property">createdAt</span><span class="token operator">:</span> Date<span class="token punctuation">,</span> <span class="token operator">...</span> <span class="token punctuation">}</span></code></pre></div> <p>That’s all. <em>new Date(str)</em> 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.</p> <p>But ther is just type mapping - the simplest case. As I wrote about <a href="/en/fun_with_json_serialisation/">in my serialisation article</a>, 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.</p> <p>Let’s say business requirements changed. You now need to group user data differently: a <em>profile</em> object for identity data, and a <em>timestamps</em> object for temporal data. The V1 documents are flat. The new V2 shape is nested:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">UserDocV1</span> <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> createdAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> lastLogin<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">UserDocV2</span> <span class="token operator">=</span> <span class="token punctuation">{</span> profile<span class="token operator">:</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> timestamps<span class="token operator">:</span> <span class="token punctuation">{</span> createdAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> lastLogin<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <h2 id="compatibility-ftw" style="position:relative;"><a href="#compatibility-ftw" aria-label="compatibility ftw permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Compatibility FTW</h2> <p>Ther is where backward and forward compatibility come in.</p> <p><strong>Backward compatibility</strong> 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.</p> <p><strong>Forward compatibility</strong> 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.</p> <p>Together:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">StoredPayload</span> <span class="token operator">=</span> UserDocV1 <span class="token operator">&amp;</span> UserDocV2<span class="token punctuation">;</span> <span class="token keyword">const</span> upcast <span class="token operator">=</span> <span class="token punctuation">(</span>doc<span class="token operator">:</span> StoredPayload<span class="token punctuation">)</span><span class="token operator">:</span> UserDocV2 <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> profile<span class="token operator">:</span> doc<span class="token punctuation">.</span>profile <span class="token operator">??</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> doc<span class="token punctuation">.</span>name <span class="token punctuation">}</span><span class="token punctuation">,</span> timestamps<span class="token operator">:</span> <span class="token punctuation">{</span> createdAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>doc<span class="token punctuation">.</span>timestamps<span class="token operator">?.</span>createdAt <span class="token operator">??</span> doc<span class="token punctuation">.</span>createdAt<span class="token punctuation">)</span><span class="token punctuation">,</span> lastLogin<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>doc<span class="token punctuation">.</span>timestamps<span class="token operator">?.</span>lastLogin <span class="token operator">??</span> doc<span class="token punctuation">.</span>lastLogin<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> downcast <span class="token operator">=</span> <span class="token punctuation">(</span>doc<span class="token operator">:</span> UserDocV2<span class="token punctuation">)</span><span class="token operator">:</span> StoredPayload <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token operator">:</span> doc<span class="token punctuation">.</span>profile<span class="token punctuation">.</span>name<span class="token punctuation">,</span> createdAt<span class="token operator">:</span> doc<span class="token punctuation">.</span>timestamps<span class="token punctuation">.</span>createdAt<span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> lastLogin<span class="token operator">:</span> doc<span class="token punctuation">.</span>timestamps<span class="token punctuation">.</span>lastLogin<span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> profile<span class="token operator">:</span> doc<span class="token punctuation">.</span>profile<span class="token punctuation">,</span> timestamps<span class="token operator">:</span> doc<span class="token punctuation">.</span>timestamps<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Look at the upcast: if the nested <em>profile</em> or <em>timestamps</em> 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.</p> <p>And look at the downcast: it writes <em>name</em>, <em>createdAt</em>, <em>lastLogin</em> as flat string fields (V1 shape) alongside <em>profile</em> and <em>timestamps</em> (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.</p> <p>You wire both into the collection:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> collection <span class="token operator">=</span> pongoDb<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>UserDocV2<span class="token punctuation">,</span> StoredPayload<span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token string">'users'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> schema<span class="token operator">:</span> <span class="token punctuation">{</span> versioning<span class="token operator">:</span> <span class="token punctuation">{</span> upcast<span class="token punctuation">,</span> downcast <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>From here, your application code only deals with V2. The collection handles the translation in both directions:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> v2Doc<span class="token operator">:</span> UserDocV2 <span class="token operator">=</span> <span class="token punctuation">{</span> profile<span class="token operator">:</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">'Alice'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> timestamps<span class="token operator">:</span> <span class="token punctuation">{</span> createdAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token string">'2024-01-15T10:30:00.000Z'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> lastLogin<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token string">'2024-06-20T14:45:00.000Z'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span><span class="token function">insertOne</span><span class="token punctuation">(</span>v2Doc<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>What’s stored (downcasted, both shapes for compatibility):</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Alice"</span><span class="token punctuation">,</span> <span class="token property">"createdAt"</span><span class="token operator">:</span> <span class="token string">"2024-01-15T10:30:00.000Z"</span><span class="token punctuation">,</span> <span class="token property">"lastLogin"</span><span class="token operator">:</span> <span class="token string">"2024-06-20T14:45:00.000Z"</span><span class="token punctuation">,</span> <span class="token property">"profile"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Alice"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"timestamps"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"createdAt"</span><span class="token operator">:</span> <span class="token string">"2024-01-15T10:30:00.000Z"</span><span class="token punctuation">,</span> <span class="token property">"lastLogin"</span><span class="token operator">:</span> <span class="token string">"2024-06-20T14:45:00.000Z"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then you can read it back with:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> doc <span class="token operator">=</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And get upcasted to V2 data in your application code:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"profile"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"Alice"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"timestamps"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"createdAt"</span><span class="token operator">:</span> <span class="token string">"2024-01-15T10:30:00.000Z"</span><span class="token punctuation">,</span> <span class="token property">"lastLogin"</span><span class="token operator">:</span> <span class="token string">"2024-06-20T14:45:00.000Z"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Same collection, V1 and V2 documents coexisting. <em>insertMany</em>, <em>replaceOne</em>, <em>findOne</em>: all go through the upcast/downcast. No batch migration needed. You roll out the new code, and old documents are handled transparently.</p> <p>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 <em>collection.findOne({ name: ‘Alice’ })</em> still works even though V2 code doesn’t use <em>name</em> 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.</p> <p>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 <a href="/en/simple_events_versioning_patterns/">versioning patterns</a> 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.</p> <p>For <a href="https://github.com/event-driven-io/emmett/pull/292">Emmett</a>, 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):</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">ShoppingCartOpenedFromDB</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> openedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> loyaltyPoints<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartOpened</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> openedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> loyaltyPoints<span class="token operator">:</span> bigint <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>And an upcast that handles each event type:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> upcast <span class="token operator">=</span> <span class="token punctuation">(</span>event<span class="token operator">:</span> Event<span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartEventWithDatesAndBigInt <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartOpened'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> e <span class="token operator">=</span> event <span class="token keyword">as</span> ShoppingCartOpenedFromDB<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>e<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> openedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>openedAt<span class="token punctuation">)</span><span class="token punctuation">,</span> loyaltyPoints<span class="token operator">:</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>loyaltyPoints<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> e <span class="token operator">=</span> event <span class="token keyword">as</span> ShoppingCartConfirmedFromDB<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>e<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> confirmedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>confirmedAt<span class="token punctuation">)</span><span class="token punctuation">,</span> totalCents<span class="token operator">:</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>totalCents<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">return</span> event <span class="token keyword">as</span> ShoppingCartEventWithDatesAndBigInt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>You pass it when reading a stream:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> <span class="token punctuation">{</span> state <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> eventStore<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">aggregateStream</span><span class="token generic class-name"><span class="token operator">&lt;</span> ShoppingCartState<span class="token punctuation">,</span> ShoppingCartEventWithDatesAndBigInt <span class="token operator">></span></span></span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> evolve<span class="token operator">:</span> evolveState<span class="token punctuation">,</span> initialState<span class="token punctuation">,</span> read<span class="token operator">:</span> <span class="token punctuation">{</span> schema<span class="token operator">:</span> <span class="token punctuation">{</span> versioning<span class="token operator">:</span> <span class="token punctuation">{</span> upcast <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Or in a command handler:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> handle <span class="token operator">=</span> <span class="token generic-function"><span class="token function">CommandHandler</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCart<span class="token punctuation">,</span> ShoppingCartEvent<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> evolve<span class="token punctuation">,</span> <span class="token function-variable function">initialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> schema<span class="token operator">:</span> <span class="token punctuation">{</span> versioning<span class="token operator">:</span> <span class="token punctuation">{</span> upcast<span class="token operator">:</span> upcastDatesAndBigInt <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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 <em>ShoppingCartOpened</em> and expects <em>clientId</em> as a flat string. But your current code evolved. Now <em>ShoppingCartOpened</em> carries a <em>client</em> object with <em>id</em> and <em>name</em>:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token comment">// What old subscribers expect</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartOpenedV1</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token comment">// What current code produces</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartOpenedV2</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> client<span class="token operator">:</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> name<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> Date <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>Upcasting enables the current code to read older events. The ones stored with just <em>clientId</em>. Downcasting helps old subscribers consume new events. It transforms the new <em>client</em> object back into the flat <em>clientId</em> 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.</p> <p>And the same upcast function that started as simple type mapping <em>string → Date</em>, <em>string → bigint</em> handles ther structural change too. You just add another case to the switch:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">case</span> <span class="token string">'ShoppingCartOpened'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> e <span class="token operator">=</span> event <span class="token keyword">as</span> ShoppingCartOpenedV1<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>e<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> client<span class="token operator">:</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>clientId<span class="token punctuation">,</span> name<span class="token operator">:</span> <span class="token string">'Unknown'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> openedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>data<span class="token punctuation">.</span>openedAt<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Old events get the <em>client</em> object synthesised from the flat <em>clientId</em>. New events already have it. The evolve function only deals with the V2 shape.</p> <p>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 <em>string → Date</em> mapping from the first example is the same code path as the <em>clientId → client</em> migration above. It’s one function, one place, one pattern for all of it: type coercion, field restructuring, schema migration.</p> <h2 id="right-decisions-stack" style="position:relative;"><a href="#right-decisions-stack" aria-label="right decisions stack permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Right decisions stack</h2> <p>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.</p> <p>Looking back, maybe the Regex wasn’t the wrong first move. The rule is <em>“make it work, make it right, make it pretty”</em>, 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.</p> <p>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.</p> <p>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.</p> <p>Full changes:</p> <ul> <li><a href="https://github.com/event-driven-io/Pongo/pull/149">Pongo PR #149</a>,</li> <li><a href="https://github.com/event-driven-io/emmett/pull/292">Emmett PR #292</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How I cheated on transactions. Or how to make tradeoffs based on my Cloudflare D1 support]]>https://event-driven.io/en/cloudflare_d1_transactions_and_tradeoffs/https://event-driven.io/en/cloudflare_d1_transactions_and_tradeoffs/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/a331c/2026-02-16-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAACVklEQVQ4y4WTy08TURSH+w+wIERDCUbRhuhWEtiwwD8AdyYmxh1gYlzj3rgyRt0RZacufCU+EqJGCYhBkEcQeZSKpdh3y7SddtpOO9N7PtOpUAok3uTkntzfnW/Oyf0dF4CI7MXBdUir7kfc212ugxBRFmIValEp18//weQ/UJejW/la5IKo8BQquYRKLKLic0gmgJRNREkDVA50sJu7HLFSRmUC2LEfqGKqXlVwFll5ggQmIelH8vq+tg/D9ipUJQNJbYKl7bUnST/q5SUqb69SmRhGlkZhZQyx7QbA/typsFLNSmmKkVWmxt+j61lHVFqU8otb2HcuYN/twnx8GWvuNTit1wFKDyOq/hNXwcih+xaYfvOMzvYTPLh/zxGtiJ/ChhczY5CPxonHCxS3t8Aq1TqoWIiRQMVWEVF14PqXMd7dHubh8E16z5xmdGQEKFDe/EZu7TuWnsbUsyRjOoZvFfI7tY8tE5UJoeLeRuA5Twe9Xefp6TzL8WOtvHr6CEwvpdAm6ZV5DC1NLpkiEtZJb2yAEUdMrQbU/KhUAFGVOnBgcBB3WxsnO07R2tbOjYErEPmEMjT0316CoSzhWJ7gnwz5SBjyUSS7hZg6KrmOGPEG+7gu9vfT1NREX18fiwvzDFy7zvL4cwhOoRI+7OAyZd8kyj8BaR+SWkOKSdTOOkrzIrbZaBu3201zczNDQ0POwczXSaY/f6w+C3ZwCRX9iUQXUJFZ1PaHmumjM0j6lzNNh2zT0tKCx+Ohp7sbLZV2XJhPhNg/WA1TYZvOCx/lwWqFfwGxGa66csdv3AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/c405bbf9b7c3fdee1d4ebfff49a57d7f/a331c/2026-02-16-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We’re being told that software design is the art of making tradeoffs. But… Are we taught how to make them?</p> <p>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.</p> <p>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.</p> <h2 id="dumbo" style="position:relative;"><a href="#dumbo" aria-label="dumbo permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dumbo</h2> <p>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 <a href="https://github.com/event-driven-io/pongo">Pongo</a> and <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a>, responsible for:</p> <ul> <li>connection pooling,</li> <li>safe connection lifetime management,</li> <li>handling transactions,</li> <li>migrations,</li> <li>SQL execution.</li> </ul> <p>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:</p> <ul> <li>How to append and process events the best way in Emmett,</li> <li>How to make the best JSON handling and translation into specific SQL dialects in Pongo.</li> </ul> <p>Dumbo usage is quite simple:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> dumbo <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/dumbo'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> pgDumboDriver <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/dumbo/pg'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pool <span class="token operator">=</span> <span class="token function">dumbo</span><span class="token punctuation">(</span><span class="token punctuation">{</span> connectionString<span class="token punctuation">,</span> driver<span class="token operator">:</span> pgDatabaseDriver <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You need to set up a specific database driver (e.g. <em>pg</em> for PostgreSQL or <em>sqlite3</em> for SQLite), as Dumbo now supports multiple relational databases.</p> <p>Having that, you can do stuff like:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token constant">SQL</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/dumbo'</span><span class="token punctuation">;</span> <span class="token keyword">await</span> pool<span class="token punctuation">.</span>execute<span class="token punctuation">.</span><span class="token function">batchCommand</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">CREATE TABLE test_users (id SERIAL PRIMARY KEY, name TEXT)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">INSERT INTO test_users (name) VALUES ('Alice'), ('Bob')</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And also do queries:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> count <span class="token operator">=</span> pool<span class="token punctuation">.</span>execute<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">query</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token punctuation">{</span>count<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">}</span><span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">SELECT COUNT(*) as count FROM test_users WHERE </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">SQL</span><span class="token punctuation">.</span><span class="token function">in</span><span class="token punctuation">(</span><span class="token string">'id'</span><span class="token punctuation">,</span> userIds<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It’ll handle query parameterisation, data escaping, etc.</p> <p>It can also handle transactions:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> pool<span class="token punctuation">.</span><span class="token function">withTransaction</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>tx<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> tx<span class="token punctuation">.</span>execute<span class="token punctuation">.</span><span class="token function">command</span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">INSERT INTO test_users (name) VALUES (</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>firstUserName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">), (</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>secondUserName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> execute<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">query</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">SELECT * FROM test_users WHERE </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">SQL</span><span class="token punctuation">.</span><span class="token function">in</span><span class="token punctuation">(</span><span class="token string">'id'</span><span class="token punctuation">,</span> userIds<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>As simple as this looks, you should already have the question about the first tradeoff I made in your head.</p> <p><strong>Why on earth I think that’s a sane move to write my own multi-database driver?!</strong></p> <p>Well, I agree, that’s not the best move at the first glance, but let me explain to you why in <strong>MY</strong> context this actually makes more sense:</p> <ol> <li> <p>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.</p> </li> <li> <p>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 <a href="https://github.com/JasperFx/weasel">Weasel</a>). And they served me well.</p> </li> <li> <p>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.</p> </li> </ol> <p>So I did.</p> <p>And here we’re at the moment when I had to cheat on transactions because of those decisions.</p> <h2 id="cloudflare-d1" style="position:relative;"><a href="#cloudflare-d1" aria-label="cloudflare d1 permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cloudflare D1</h2> <p>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:</p> <ul> <li>What’s the difference between databases like PostgreSQL and single-threaded databases like SQLite or DuckDB, and how concurrent processing can be surprising</li> <li>or that sqlite3 only calls the first query, but the next silently ignores,</li> <li>etc.</li> </ul> <p>Still, well, abstractions like Dumbo give the possibility to <em>massage</em> such cases behind the scenes.</p> <p>And then Cloud Databases and SaaS Databases came into play, like Cloudlfare D1.</p> <p>I was motivated by the generous sponsorship from <a href="https://www.linkedin.com/in/samhatoum">Sam Hatoum</a> to make supporting Emmett and Pongo a top priority. Thanks Sam, appreciate that! And I made it, but I had to cheat a bit.</p> <p>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.</p> <p>They do it by exposing the database API through HTTP API, for instance <a href="https://docs.postgrest.org/en/v14/">PostgREST</a>. This gives them easier management around throughput, security, etc., as each call is made as an HTTP request through the exposed API.</p> <p>Yet, that kills some options like: ekhm, transactions. The challenge with transactions is that they don’t scale (that’s why <a href="https://www.youtube.com/watch?v=b2F-DItXtZs">MongoDB is WebScale</a>). 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.</p> <p>But, boy, aren’t transactions one of the selling points of relational databases? They do, so how to proceed?</p> <p><strong>Then you need to cheat, as I did.</strong></p> <h2 id="repeatable-reads-batches-etc" style="position:relative;"><a href="#repeatable-reads-batches-etc" aria-label="repeatable reads batches etc permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Repeatable reads, batches, etc.</h2> <p>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:</p> <ul> <li>sessions,</li> <li>SQL batches.</li> </ul> <p>What are sessions? <a href="https://developers.cloudflare.com/d1/best-practices/read-replication">Per Cloudflare Docs</a>:</p> <blockquote> <p>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 <a href="https://developers.cloudflare.com/d1/best-practices/read-replication/#replica-lag-and-consistency-model">sequential consistency</a> for all queries in a session.</p> </blockquote> <p>Essentially, that means that we’re getting <a href="https://jepsen.io/consistency/models/repeatable-read">repeatable reads</a>. So when we’re starting specific sessions, they will be handled sequentially.</p> <p><strong>And now, the second ingredient: Batches. Per <a href="https://developers.cloudflare.com/d1/worker-api/d1-database/#batch">Cloudflare docs</a></strong></p> <blockquote> <p>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.</p> <p>Batched statements are <a href="https://www.sqlite.org/lang_transaction.html">SQL transactions</a>. 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.</p> <p>To send batch statements, provide D1Database::batch a list of prepared statements and get the results in the same order.</p> </blockquote> <p>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.</p> <p><strong>Cool, let’s mix this soup together, as having that, I decided to:</strong></p> <ol> <li>Fail automatically if someone tries to create a transaction on Cloudflare D1 with an error:</li> </ol> <blockquote> <p>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.</p> </blockquote> <p>Then they get clear information on the first try.</p> <ol start="2"> <li>Allow users to explicitly open a session-based transaction by providing mode:</li> </ol> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> pool<span class="token punctuation">.</span><span class="token function">withTransaction</span><span class="token punctuation">(</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>tx<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> tx<span class="token punctuation">.</span>execute<span class="token punctuation">.</span><span class="token function">command</span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">INSERT INTO test_users (name) VALUES (</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>firstUserName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">), (</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>secondUserName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> tx<span class="token punctuation">.</span>execute<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">query</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">SELECT * FROM test_users WHERE </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">SQL</span><span class="token punctuation">.</span><span class="token function">in</span><span class="token punctuation">(</span><span class="token string">'id'</span><span class="token punctuation">,</span> userIds<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> mode<span class="token operator">:</span> <span class="token string">'session_based'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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.</p> <p>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 <em>“scarification”</em> sometimes.</p> <p>Still, when I implemented that in <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a> and <a href="https://github.com/event-driven-io/pongo">Pongo</a>, I intentionally used it to enable event appends, but document operations, etc.</p> <p>If you’d like to try it, you can check Emmett’s or Pongo’s beta versions.</p> <p>For Pongo, you can install it with:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> @event-driven-io/[email protected]</code></pre></div> <p>And use it as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> d1PongoDriver <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/pongo/cloudflare'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span><span class="token punctuation">{</span> driver<span class="token operator">:</span> d1PongoDriver<span class="token punctuation">,</span> database<span class="token punctuation">,</span> transactionOptions<span class="token operator">:</span> <span class="token punctuation">{</span> mode<span class="token operator">:</span> <span class="token string">'session_based'</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Or in Emmett by installing:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> @event-driven-io/[email protected]</code></pre></div> <p>And use it as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> getSQLiteEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-sqlite'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> d1EventStoreDriver <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-sqlite/cloudflare'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> <span class="token function">getSQLiteEventStore</span><span class="token punctuation">(</span><span class="token punctuation">{</span> driver<span class="token operator">:</span> d1EventStoreDriver<span class="token punctuation">,</span> database<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[On rebuilding read models, Dead-Letter Queues and Why Letting Go is Sometimes the Answer]]>https://event-driven.io/en/rebuilding_read_models_skipping_events/https://event-driven.io/en/rebuilding_read_models_skipping_events/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/15181cbf6a5249da225a8603462fe780/a331c/2026-01-19-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC80lEQVQozxXLWW/aBgAAYP++aaq052naHvKwS9NaTVM6qdI0pK6aJkpa9UgpicJCQgsNhFASJwYMBDA2t2NjY2xjfJ8YAyEhUE393j/gy38/fr19/iACP0lVQjksgXaLZI9meiORHem85go4yZKc1JOYKk2BLTxSrD8/Q3yJyvd7OeCeP/Nt8GIzWgxkkP1S87yFD8mu0a5pbdSzhPmtpkgjfqRKtshqcoXuV/t8nuC2s62fw3ngq8Dpxg70KFZ6AdYOq50aTpDpZOHVSyqdWtnKemXPJo5mGs7cGs+mlCy3h+IRSvgSpfsRGLj3NLOxV9iMlf1n6H65U+4PwEDg5KFvEEtTZ9Byqjq2a7m6t/Dc+ZRRlBI5eJvFHifLn/Nve7noeQXMV6qNTrbda0ganDzZf/g3FUvzlzVDUQVBvblb2FNXtAxClDtDKXfFPgPrP4VzwDcvwN8jhdcfoMt0hviYpIu5fCh0ev8PdmuHiWfqdUbSDckxRFNnNZ2QZAhnThpUpEx8FwSBL/yZH0LZvxLYa6ibhhD4OJN49iq2/b4ZTs6yJYuX+7oxsixnPlfHE1LS/it1aoz851FlYzcHbLyBt6IwGo4PkVb04uoQZk9RKZ5n4dpwgAssJTCqKdiOMZmKtpsjuAavHFTpXw5Kvx5WgKdHnefx2ptY8Z8UFgA7wUwzXu5HjpHtd2V/HNmB8FSDwwWNM8bHzUGywbzHWD/Y9R23fgwXAEbw8m315XnvbYGGSY1Qva48CUH9x1HkURDcSmE7RarQU1tDByKUgwrzDmE/oHy0PAhd9IDxZCUbC4yyutyY16+HxvVZXXxyhG8GS75daLdEJ1tiU3CV8ZKUvRypoqwN4WqZ0DHSAFxvNZndebOVMV5y2hzn3QKuXxJmBlMglIdwBe6bonNjTteifUtIHil5DcbuDpxGzwSWt5/Wq0+LxZpTrxHarvcdRpqNzBvVWQ7VOSF7V+psMl+587Xu3onmDTXymrRFcm7tSv8fvbBn6kSEIrYAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/15181cbf6a5249da225a8603462fe780/a331c/2026-01-19-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In the last article, I explained <a href="/en/rebuilding_event_driven_read_models/">how to rebuild Event-Driven Read Models in a safe and resilient way</a>. I asked readers to let me know if they find any blind spots in my design.</p> <p>Well, I found one myself.</p> <p>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.</p> <h2 id="everyone-has-a-plan-until-they-get-punched-in-the-mouth" style="position:relative;"><a href="#everyone-has-a-plan-until-they-get-punched-in-the-mouth" aria-label="everyone has a plan until they get punched in the mouth permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Everyone has a plan until they get punched in the mouth</h2> <p>Let me recap the situation described in <a href="/en/rebuilding_event_driven_read_models/">the previous article</a>. 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.</p> <p>In the design from my previous article, during a rebuild, we:</p> <ol> <li>Mark the projection as “rebuilding”.</li> <li>Skip inline projections (the rebuild process will catch up anyway).</li> <li>Process all historical events.</li> <li>Mark the projection as “active” again.</li> </ol> <p>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.</p> <p>Sounds solid. Here’s where it breaks:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f8043733ebf202e179c6672d29b0caa9/7b314/2026-01-19-mermaid1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 68.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB4UlEQVQoz2WSy25UMQyG+/4Pg4QoQgJKF1C1i4LYIHVHO51zTU7udyc2OnOm7ZRaUaQ4/uLY/s+ICHTTe2e6UF2jgwHiBGkf7FBibqsTiaiEtAxpGSi5LeyMiFqkKFOUCTNu3oZoauHBK8iAhNUG/TPya9tfmO4i8utkflEzZ1t0OVhrzbkQYyoFiCj6uKVsoC2/cvJWsRvNbpy8dcsVVnWEcypKmXniXTd23TAOszFWSU2vrZQK0J6PL5ml0GxexpHNE59GNo1MqRVGxMPWiLDkVEqhg3eFa571+EUMX/u/5+PDJ295CDn46H3Q2rzJDFtFx8xYY45dsI/BPtYyEb7cOedLKcY4pbSSIgaXoi8lEjWiSoTP366nT57AOSUIfpke3k/37/juveg+qP5c9h+x2tNu5/9g77yzTggpFjFPHZt7Jdk6vuJLdoTtCMNazDoqgNaQDrogayPnhnO9LibZLMSiX9V8HFUuWptlUYzNU/en3/2W7E7OdxB2JewgPmLLW9gbGAkS5LDOAFs07IcYvs3DRb//zKdLMVxa9h3rKknIHlI4NOwJBt3k3unOw4m2OcIQ/dxKxidtJxXYfWD3FMSJtgtmB8VBgydtE7oKumRboeLRSTXXoGswCGmD/wEYaSauKQp+KQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid1" title="mermaid1" src="/static/f8043733ebf202e179c6672d29b0caa9/a331c/2026-01-19-mermaid1.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>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.</p> <p>We could handwave it with <a href="/en/no_it_can_never_happen/">it won’t ever happen!</a>. 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.</p> <h2 id="the-rabbit-hole-of-fixes" style="position:relative;"><a href="#the-rabbit-hole-of-fixes" aria-label="the rabbit hole of fixes permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The Rabbit Hole of “Fixes”</h2> <p>My first instinct was to engineer my way out of this. Surely with enough clever coordination, we can close this gap?</p> <p>Let me walk you through the rabbit hole.</p> <h3 id="attempt-1-wait-for-in-flight-transactions" style="position:relative;"><a href="#attempt-1-wait-for-in-flight-transactions" aria-label="attempt 1 wait for in flight transactions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Attempt 1: Wait for In-Flight Transactions</h3> <p>PostgreSQL provides <em>pg_snapshot_xmin(pg_current_snapshot())</em> 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.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6e98d7a96879259a92a8c0d1d4c28642/804dc/2026-01-19-mermaid2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 52.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAABeUlEQVQoz1VS247UMAzt/38OYgVCQvzEvsELojtNm0vTJHYuTpwl7TCzOJFlWzq+HU+99xa6X2LaSs/9IZZpjmHJgK3eQ8xDowHxK+vfvcA0gqVHm7wCf0CMMQQAQOX9FvxRMnH7l/AEE6LdKOy9lukKI0bnvdbm9iaWNyHEFjwc+9FqO0s+pfceU25txCdj9ko1eAg+QEB3eO+Cc14pM8+Lta7+L621EIAKDTDRGCmnciX7KCHAbqzW+6Wl1Kc93HIHF0fZgVcJd8pHq/EBBkBzYpTSSupVyG1TShol9R0c1A+7fLHi6yG+HesLwetjNynlq9PrEw2HmRGwlHK1nSgf/pAIhrKvRESVaMzmxwp3uSmljJRarNu2jspSaqKzciNtby/6z2czf3Lrd2Zi7tdaAWMuhZlb4/FOg7klxHaSP40Okf0tRJl65sfAe6MZ3JLxeSQXz9F48TONI8HBcytcQiVoTE9w5OqojDGYP1LAFAltje7vkbwDOm98BzcsC7wAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid2" title="mermaid2" src="/static/6e98d7a96879259a92a8c0d1d4c28642/a331c/2026-01-19-mermaid2.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Why it fails:</strong> 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.</p> <h3 id="attempt-2-use-transaction-ids-as-boundaries" style="position:relative;"><a href="#attempt-2-use-transaction-ids-as-boundaries" aria-label="attempt 2 use transaction ids as boundaries permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Attempt 2: Use Transaction IDs as Boundaries</h3> <p>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?</p> <p>The logic would be:</p> <p>When rebuild is ready to complete, record sealing_txid = current_transaction_id</p> <p>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)</p> <p>Sounds reasonable?</p> <p>Here’s why it doesn’t work.</p> <p><strong>The core problem: transaction ID order ≠ commit order.</strong></p> <p>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).</p> <p>Let’s trace through a concrete scenario: <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2943d3babe77b71ff4bc14af0afbb9be/186ec/2026-01-19-mermaid3.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 86%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAIAAABSJhvpAAAACXBIWXMAAAsTAAALEwEAmpwYAAACJUlEQVQ4y32TyW4UMRCG8/5vRCTCIqFcOXDgAELJZGbS7X0pV5XLC+rpACFEWCXL2+f6bf++mnMOIVisu19qzkeI34yxVHpa/fl7Deu2YG4FxjxQfWApYx+YV3NO9J/dwxtzfx3Xd1hTIm5jUAFIQSqNX0vHnJm4SHvq7/Ccs/WeMjALIeUMIk1rdzw+hpiZ6x61So65tTZfwL0P7zMAx1S0ts4FtZofP+6Vska7GFPOAIDGOJFncBcX1YeoPqyHt+Z0Df5rzoxIpWCMCZEQqVYRaSINoPydechovlXHqOcIvZVSqEmjQsui1lVb67yPWEiq5ASvyyaql0ZPCYjI+7Asaq+VMuuqETGE9Jfs/SJ7b0S0w8zbLmOMnLGUqrU/nxatbc7F2vAvvDFE/AyW0VIMq1EPWqvHR7WuRmunlGmt/wduXEfjU1hu7Om9O99APNY6CLG1Xgq+eubnsnmXnTJCYe/j+bzJBkDnXpc9iGWHAVBEmNm5YI2z1lvrSyFEdi6+hLtEyvc53Amr3vv+vDlDCMn7aIxDJIBSCjrrReTi1C02WPCY9G1YbzF8aU2QmJk3k4TEzEQ8nr7GJJb2x9rPLqxglTZ2b8MlrbU+Z0gJIR7M6caeP+qHm6Q+ZXOb9O0Qd4HbREPxnAX7QnAXfRQeYMAcGpiLDXhUk9kesjpklcg0NmPUq8vcpFjBYuPuGFfIudZeYvGrlPBbZJ3TtS3qL+E/Ac/s1dOcZPnDAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid3" title="mermaid3" src="/static/2943d3babe77b71ff4bc14af0afbb9be/a331c/2026-01-19-mermaid3.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Here’s what happened:</p> <ol> <li><strong>Tx 99</strong> 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.</li> <li><strong>Tx 100</strong> (the rebuild) started second, recorded <em>sealing_txid = 100</em>, and prepared to complete.</li> <li><strong>Tx 101</strong> started third. It checked: “Is my transaction ID (101) >= sealing_txid (100)?” Yes, so it assumed the read model was ready and processed its inline projection. It committed successfully.</li> <li><strong>Tx 100</strong> marked the projection as active and committed.</li> <li><strong>Tx 99</strong> 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.</li> </ol> <p>The fundamental issue: <strong>we can’t see uncommitted transactions.</strong> 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.</p> <p>You might think: “Just wait until all transactions before the sealing point have committed!” PostgreSQL even provides <em>pg_snapshot_xmin(pg_current_snapshot())</em> 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.</p> <p>I wrote about this exact problem in <a href="/en/ordering_in_postgres_outbox/">How Postgres sequences issues can impact your messaging guarantees</a>. 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.</p> <h3 id="attempt-3-lock-appends-during-transition" style="position:relative;"><a href="#attempt-3-lock-appends-during-transition" aria-label="attempt 3 lock appends during transition permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Attempt 3: Lock Appends During Transition</h3> <p>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?</p> <p>Here’s the proposed flow:</p> <ul> <li>Rebuild finishes processing historical events</li> <li>Acquire exclusive advisory lock (all new appends must wait)</li> <li>Set status = ‘active’</li> <li>Release lock</li> <li>Waiting appends resume and sees ‘active’, processes inline normally</li> </ul> <p>It should work. In theory. We’re creating a synchronisation point where nothing can slip through. But there’s a subtle problem: <strong>the skip decision was already made inside the append transaction.</strong></p> <p>An append transaction doesn’t just check the lock and status at one instant. It does several things:</p> <ol> <li>BEGIN transaction</li> <li>INSERT the event</li> <li>Try to acquire a shared advisory lock</li> <li>Check projection status</li> <li>Decide: process inline or skip?</li> <li>Execute that decision</li> <li>COMMIT</li> </ol> <p>The decision in step 5 happens <em>inside</em> 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.</p> <p>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?</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/9bd7348156952524b739722203592523/f3dd3/2026-01-19-mermaid4.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 90.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAAsTAAALEwEAmpwYAAACTUlEQVQ4y42Ta0sbQRSG8/9/SL9UwSIUG6zWtH4phRbEglRjTfY2u7M79/vlyG4SSaBiD8PAnplnzsz7np0BQDJACzMUipVGYw9TkOjXildOmxQ3GY3XtLrX7SPktMnMALLsrvHfj/3Tp2E1p+VZ9ggAImRhjQ4+Qd5sheis5NFp2MVsnBKDTKxGVrfedZRgY1xKSQmVYoK9sHbMH8B5Olpri1C/WtXFui7WVdO0GA8xxjfg3YLn3LQtbdu+rhEhVHDxNuz1UpEfrPuOq+uu+CbFwIVSSvP/gZ1pFLuj+Dfpbnt0IziRUnvvpVRx++Y8DbDWplH87ecIG/ZL4AXvrob6slvPGVlxoa11e/DrlaMn3iItSmdQChggvGyllA8D6XFD+pKRRokuRwlJQdIAeQY55tEq4WyfAoXEclQ7/Y0xJkYQwy0u5m1xgcsL1n5l7ZUiP8fKOQraXJD6vC/mQ3VO6jMn73aV7XTJHMzKqaVTj2K40/zeygcjlznZA6te3rOpPME5BsfQl776zLsFQ5cCX8l+IfAiBTrbiQnO2X0xAEApxRhr265peoSIEFZKl1L+R5MYYynljHLGFGVGCEupVjpI5YW0QkijtZQyxcmq8W75AJZKGRt4f1Pev6se3jfLI1qf0uZUtGeQ7SvtOXnujEsxAyTZnfPmhKEPXXnclcekOunXR07+GWGtUgwHcLJZNIo1kiNtBgcp5pxZ9I3mndcmhen4aGnN0ZPt1/v/8+i0E2EaMZhtM5uUWPA8hpC3CkUjvKJRM9hlngFRPgpzJv120gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid4" title="mermaid4" src="/static/9bd7348156952524b739722203592523/a331c/2026-01-19-mermaid4.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>The timing here is tricky. Advisory locks in PostgreSQL can be either:</p> <ul> <li><strong>Transaction-scoped</strong> (<em>pg_advisory_xact_lock</em>): released automatically when transaction commits</li> <li><strong>Session-scoped</strong> (<em>pg_advisory_lock</em>): held until explicitly released or session ends</li> </ul> <p>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.</p> <p>Even if we could perfectly synchronise the lock acquisition, there’s another problem: <strong>advisory locks are session-scoped, not durable.</strong></p> <p>If the connection dies while holding the exclusive lock:</p> <ul> <li>The lock releases immediately (that’s how advisory locks work)</li> <li>We might have partially updated the status</li> <li>The system is now in an unknown state</li> <li>Other transactions resume with potentially inconsistent data</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6fe525cb478c19689e763e530ba1eddd/0a09a/2026-01-19-mermaid5.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAABsElEQVQoz22Sy27bMBBF/f8fkTqrLvoJrpGiBoouk0URFEksu04UkRRFSRQf4jxYOIrswuldkjxz78xwQX3uX70XI/k8CZlLCKUfBMRAlHPmnHNsvSq82rOr86wF+ex0iCZR5OmImDWO0lvpBw+JEAARfBtMGUzFsXurdny8OBYNEQFP9ZiPF23fC1lLoaTshOzq2vZ9jDHlf/QGx4h4Cfe9cw607lV5W+5/NvLONr9ce4/hCfwDhi0zzc5nmGn8Q+PedQ/BPlLcp2ajX1Z1uZbPK3FYmeqmKded/M6MlzBDbQ5LWVzL4lpsl2p3hWM53SBSjOOH2O/wMS2EF334ooorWSzV7pPafw72cYYhhjD3xeeeAYCIJv847HT51VSbTtwk/zSN4bhCxBjix4ElY0JVNaJStdJCaK17az29LXky+T+M4bc1t9VhUz3fad0qpZWqjW66pp3GfhIihQu4e/2him9quw7t/bwpDoSt8w7SmSagOIxDl+m86gXb3FcxSODL72mr5B3Ce+5gnNgOcseuPiVa0MjjkNIANNtwzhbBQrIIeEqOEX0LvuPkT85/Ae2h6/STZWC1AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid5" title="mermaid5" src="/static/6fe525cb478c19689e763e530ba1eddd/a331c/2026-01-19-mermaid5.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>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.</p> <h3 id="tldr-on-attempts" style="position:relative;"><a href="#tldr-on-attempts" aria-label="tldr on attempts permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR on Attempts</h3> <p>Every “fix” follows the same pattern:</p> <ol> <li>Identify a coordination point.</li> <li>Discover there’s a window before that point we can’t see.</li> <li>Try to close that window.</li> <li>Create a new window somewhere else.</li> <li>Repeat.</li> </ol> <p>We’re not solving the problem. We’re relocating it.</p> <p>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.</p> <h2 id="stop-fighting-start-tracking" style="position:relative;"><a href="#stop-fighting-start-tracking" aria-label="stop fighting start tracking permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Stop Fighting, Start Tracking</h2> <p>After chasing my tail through various “solutions,” I stepped back and asked a different question.</p> <p>Instead of: <em>“How do we prevent events from being skipped?”</em> (which requires blocking appends or seeing into uncommitted transactions—both unacceptable because of performance, guarantees etc.)</p> <p>I started to think on: <em>“How do we know when an event was skipped?”</em> and <em>“How do we ensure skipped events get processed eventually?”</em></p> <p>Both of these are solvable.</p> <p>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.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d783b0d5b838843e333dee2672ae3f53/91948/2026-01-19-mermaid6.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 71.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAABnUlEQVQoz32SS27cMBBEff9jZOEEOUIu4VUAZ2F4MhL/okiJP1HsbgWj8RjQBJ5CoVZ86Aa7njbacN6ax6QLuA0SbjfRnhGagcpzVDXLVoDo88HTFV5sm0REv+F/cEYwuPZxFiWJteAB3gUA0xSWWpdaW4OUctfxlMq6trqsta6j9a217ahPGK11y1KtdYzJwYxKGqWMEHq0rtYqpWkNvoLB+ymEKITue86ZZEwoqYVQjMkY4zCMj2DnJu9nra0x1uzpxslaJ4X2fjbGPoDR+xmRQohaDVoPQuhrplQQyVoP8DU8jr7r+PnMGJecKyk1Y5z1/HzuOeNK6dbW/QJ482HylHNOKeecvZ84V9O85AIxrSnDYMOjta31OZcUr3DgjDnzex5fJ/sa3B/ZvyzprZXT7nes4gCnlA/toOTYT316Nqfv5pLP+v1i8/eHePs2q18fDaOwrQ6jKjhtmD8KRBfBRpChDlBYcGoJek2IbSMgwls9PdWhzTw2SxDu6xmgSVi6OPOS+prhrp7UCCvRuifS3a8QUQEoePFyvNY/K/cnNLuOBEEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid6" title="mermaid6" src="/static/d783b0d5b838843e333dee2672ae3f53/a331c/2026-01-19-mermaid6.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>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.</p> <p>In Emmett, I’m using a dedicated <em>emt_system_messages</em> table rather than reusing the regular <em>emt_messages</em> 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?</p> <p><strong>Why a dedicated system messages table?</strong></p> <p>The regular <em>emt_messages</em> 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.</p> <p>The system messages table could mirror the structure of regular messages with:</p> <ul> <li>Global position sequencing (with all the transaction visibility handling from the <a href="/en/ordering_in_postgres_outbox/">outbox ordering article</a>)</li> <li>Transaction ID tracking for proper visibility checks</li> <li>Archiving support via <em>is_archived</em> flag</li> <li>Partitioning for performance</li> </ul> <p>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.</p> <p>The skip could be stored as a system message where:</p> <ul> <li><strong>Stream ID</strong> = the projection identifier (name + version), so we can query all skips for a specific projection</li> <li><strong>Message data</strong> = reference to the original event (sequence ID from the event log)</li> <li><strong>Message metadata</strong> = processor ID, reason for skip, timestamp</li> <li><strong>Message type</strong> = indicates this is a “skip during rebuild” system event</li> </ul> <p><strong>Won’t this table grow too much? It may, but we can archive skip events when they’re no longer needed.</strong></p> <p>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.</p> <p>Why archive instead of delete?</p> <ul> <li>Audit trail: You can see what was skipped and when it was processed</li> <li>Debugging: If something goes wrong, you have history to investigate</li> <li>Idempotency: If a processor crashes and restarts, it won’t reprocess already - archived skips</li> </ul> <p>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).</p> <p>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.</p> <p>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.</p> <h2 id="the-final-flow" style="position:relative;"><a href="#the-final-flow" aria-label="the final flow permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The Final Flow</h2> <p>Let me walk through how this works in practice, bringing together all the pieces.</p> <h3 id="during-rebuild" style="position:relative;"><a href="#during-rebuild" aria-label="during rebuild permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>During rebuild</h3> <p>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.</p> <h3 id="when-rebuild-catches-up" style="position:relative;"><a href="#when-rebuild-catches-up" aria-label="when rebuild catches up permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>When rebuild catches up</h3> <p>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.</p> <p>But what about those skip records? The events they reference exist, but their projections were never applied.</p> <h3 id="draining-the-skipped-events" style="position:relative;"><a href="#draining-the-skipped-events" aria-label="draining the skipped events permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Draining the skipped events</h3> <p>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 &#x3C; pg_snapshot_xmin), it only sees skip records from committed transactions.</p> <p>For each skip record:</p> <ol> <li>Find the referenced event in the event log (or use its data if stored in skipped event)</li> <li>Apply the projection for that event</li> <li>Archive the skip record (set is_archived = true)</li> </ol> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/9b10c8241901d1b66ba9c29d4a150f02/b229a/2026-01-19-mermaid7.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 90.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAIAAADUsmlHAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOElEQVQ4y3WTy24VMQyGz/u/RiUWCN6hVOwRZcMGCj0zZxLnfk8miYNmpqpoe2pl4Uj5Yv+2/tPAgX4kXsziIy/NIHYcb6IjslZ4LRfvRFtZLR3xNHB0NwJk/qgdjVX1A8aneIIbItTEa3jUnNcAa+w4Ttu7hqOOaFJLbfS3Vcfxg5Ff/j7cAPn8+OfGyrsxdviIEGKMKW9RYkwp5RjTluQcY04pW/HT8m9OfI/6vuXzC9i7wEAwEEA5IQDACWHzTDgTAJwSBqC0jlK4Z+R0yBtjCKG8D8HH5UIZcKCMAWfAlwWkUJeZaKVzSkqqXQe+gKXUMabeu3NeSm20lUJpbY2xSmrnfGttXauz/nmMp+eBWOOMsTEmKdTWJOXLhdK9PiUghNpkW8eZeNH2ARvjnPVrWSll07SQBQiBXbCYp4WDqOsafJBSv4THU2XvfSm55BJCZEwwJgSXgqsQji3kEKIQ6tXAtmyTp2wMyVlPKZ9nMp0v87TM0wLAvQsxJGsc5/LKtLXWSqoQglZmnhcGghJGCCMLLBdqjQs+Gm0ZiCuVlbKlrL2PWhsiehc4k2QBY7bF1tpqbWVdtyv+B/d0ry6fyMPH5feHpO5arcfT/Rx5ba311lqt/tWqevilz7d6+qrOt0X/GO/Eihh724S1umLf4d2SRXd1yUm0bgZe80bfXJVZzWejec1Q827JMbrDAFHNxtO4qnrdzwNZzbCmyRnYEzxg7Igrjjawjl7x/bZ76q3szR9t/wOhpQ3AbmGA1QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid7" title="mermaid7" src="/static/9b10c8241901d1b66ba9c29d4a150f02/a331c/2026-01-19-mermaid7.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>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.</p> <p>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.</p> <p>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.</p> <p>This flexibility lets user choose the approach based on the specific operational requirements.</p> <h2 id="the-dead-letter-queue-pattern" style="position:relative;"><a href="#the-dead-letter-queue-pattern" aria-label="the dead letter queue pattern permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The Dead Letter Queue Pattern</h2> <p>What we’ve built is essentially a <strong>Dead Letter Queue (DLQ)</strong>—a place where messages that couldn’t be processed normally are stored for later handling.</p> <p>This pattern exists in every serious messaging system:</p> <ul> <li><strong>Apache Kafka:</strong> Dead Letter Topics for messages that fail consumer processing</li> <li><strong>RabbitMQ:</strong> Dead letter exchanges for rejected or expired messages</li> <li><strong>AWS SQS:</strong> Redrive policies that move messages to a DLQ after N failures</li> <li><strong>Azure Service Bus:</strong> Built-in dead-letter sub-queues for each queue</li> </ul> <p>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.</p> <p>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.</p> <p>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.”</p> <p>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.</p> <p>That’s why in the discussed design, skip records aren’t meant to accumulate indefinitely. The rebuild processor drains them during completion.</p> <p>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.</p> <p>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.</p> <h2 id="the-broader-lesson" style="position:relative;"><a href="#the-broader-lesson" aria-label="the broader lesson permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The Broader Lesson</h2> <p>This article isn’t really about PostgreSQL advisory locks or projection rebuilding. It’s about how we approach problems in distributed systems.</p> <p>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.</p> <p>At some point, I had to ask myself: am I making this system more reliable, or just more complicated?</p> <p>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?”</p> <p>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.</p> <p>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.</p> <p><strong>Sometimes the best engineering decision is to accept what you can’t control and focus on what you can.</strong> 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.</p> <p>What we can control is recording skipped items, ensuring those skips are processed, and making the whole thing observable. That’s enough.</p> <p>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.</p> <p>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.</p> <p>Something has to give.</p> <p>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.</p> <p>That’s the kind of system I can reason about and trust.</p> <hr> <p><strong>If you’re dealing with similar challenges, I’m happy to help through <a href="mailto:[email protected]">consulting or mentoring</a>.</strong> You can also join the discussion in our <a href="https://discord.gg/fTpqUTMmVa">Emmett Discord</a>—we have a nice community working through these exact problems.</p> <p>Or check also other related resources:</p> <ul> <li><a href="/en/rebuilding_event_driven_read_models/">Rebuilding Event-Driven Read Models in a safe and resilient way</a></li> <li><a href="/en/ordering_in_postgres_outbox/">How Postgres sequences issues can impact your messaging guarantees</a></li> <li><a href="/en/projections_and_read_models_in_event_driven_architecture/">Guide to Projections and Read Models in Event-Driven Architecture</a>,</li> <li><a href="https://www.architecture-weekly.com/p/distributed-locking-a-practical-guide">Distributed Locking: A Practical Guide</a>,</li> <li><a href="/en/consumers_processors_in_emmett/">Consumers, projectors, reactors and all that messaging jazz in Emmett</a>,</li> <li><a href="/en/how_to_scale_projections_in_the_event_driven_systems/">How to scale projections in the event-driven systems?</a>,</li> <li><a href="/en/checkpointing_message_processing/">Checkpointing the message processing</a>,</li> <li><a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Rebuilding Event-Driven Read Models in a safe and resilient way]]>https://event-driven.io/en/rebuilding_event_driven_read_models/https://event-driven.io/en/rebuilding_event_driven_read_models/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/9e38ed3c86bf4b6ef2a88b6e89966001/a331c/2026-01-05-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAACpElEQVQ4y12TWY8jNRSF8//fEOKBRfPAA4sEAo3U0ohhFtGtEUM66ZB0Okmnk9ReZZddVXZVZWFG+pCdgBAPR9fXur7LuccD3XSoukVqS2V6nF/WHZXdI7Wh3X+gO3ykaQ+Y7oTp/0I3PVXTo1yc6THd0d9JZRjY/oRu9lRmT+1gz8il5osvn/Ht9z/yzXc/8PPzK356fsWrtzf/Fs2lIZUlQRoSFxmqahmMJnOG43t+ezdkOl/7wCRXPKy23N7NuXrxil/f3PDHaMpwNOX2bsZitSHKpO8qygtWuw1ZISldwpevr/n0s8/56tnXLB53SN1SKEOh7GX0A6rpyWRDJhpvr9/PGc5C1rHhYVeyCDSbxJKXhoF7uI1ywrRENXtk1SF0i6xaSsdtdR6vrHtvnb+OLbNNxd2yYL6tGS8K7+eqY+ACtNlTNQalNVVdU+raJ3BJhbbkynpbXpIb23E4njiePrA/HLFthzEWVVkGbiRd1eTBguhxSvw4pch2JEqhTYftTvT7o19UdcEuznkKUk+Na8bBFXNKGbjKUjdkaUgab4mCDXkhSYQmFimTXc6LSUIsjJeJo+P9LOHtbcD1OGS4kAwfBNON49cwcEQWskImW8L1PcFygixiIqEJs5g30xmfXP3JMsyI8pxdlpOXbmEH6u4jTXfkafmO4GlIqRvXYYtQDWkckIRrkuARIQpiqYhFxmxX8HKSEBY1srIU2lBWZ77uH1YMR2OyPEHr8ixsJxNRarLdgu1iQrgck0RPhIXyEim1pW5aitJ4WRSl9ffufPP7iF9eX5MKg9AdwslmFRTEmfDVK9ujmtZv0v0ebR3hvdehvsCfTe+/XG0P1O3B+24hvsNNJIhSgVAG1XSIi0TcArT5T6L6Hz2e8f8ipZ/U8DdhPcBoytU+RQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/9e38ed3c86bf4b6ef2a88b6e89966001/a331c/2026-01-05-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Let’s make a soup today: a blog soup. We’ll mix multiple ingredients like:</p> <ul> <li>events (obviously),</li> <li>read models,</li> <li>inline and async projections,</li> <li>rebuilding read models,</li> <li>backfilling new ones with data from existing events,</li> <li>scaling async processing horizontally,</li> <li>distributed locking,</li> <li>PostgreSQL and its Advisory Locks.</li> </ul> <p>Sounds a lot? Well, the soup should be nutritious.</p> <p>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.</p> <p>Typically, we’re using events in two ways:</p> <ul> <li>reacting to them, triggering and integrating the steps of our business workflow,</li> <li>projecting them, and getting the flattened interpretation of our system state inside read models.</li> </ul> <p>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 <em>inline projection</em>. Event stores like <a href="/en/emmett_projections_testing/">Emmett</a> and <a href="/en/projections_in_marten_explained/">Marten</a> allows that. Still, you can do the same if you’re using <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">outbox pattern</a>, then you can achieve the same without Event Sourcing.</p> <p>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.</p> <p>Also, as <a href="https://www.linkedin.com/in/bastian-waidelich-84865221">Bastian Waidelich</a> 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).</p> <p>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.</p> <p>The big benefit of a durable event log is that we can correct past mistakes and gain more insights from existing data.</p> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">ShoppingCartSummary</span> <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItemsCount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> totalAmount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> evolve <span class="token operator">=</span> <span class="token punctuation">(</span> document<span class="token operator">:</span> ShoppingCartSummary <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> event<span class="token operator">:</span> ProductItemAdded <span class="token operator">|</span> ProductItemRemoved<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartSummary <span class="token operator">=></span> <span class="token punctuation">{</span> document <span class="token operator">=</span> document <span class="token operator">??</span> <span class="token punctuation">{</span> totalAmount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAdded'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'adding'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemoved'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'removing'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> withAdjustedTotals <span class="token operator">=</span> <span class="token punctuation">(</span>options<span class="token operator">:</span> <span class="token punctuation">{</span> document<span class="token operator">:</span> ShoppingCartSummary<span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> by<span class="token operator">:</span> <span class="token string">'adding'</span> <span class="token operator">|</span> <span class="token string">'removing'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> by <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">const</span> plusOrMinus <span class="token operator">=</span> by <span class="token operator">===</span> <span class="token string">'adding'</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>document<span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> document<span class="token punctuation">.</span>totalAmount <span class="token operator">+</span> productItem<span class="token punctuation">.</span>unitPrice <span class="token operator">*</span> productItem<span class="token punctuation">.</span>quantity <span class="token operator">*</span> plusOrMinus<span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> document<span class="token punctuation">.</span>productItemsCount <span class="token operator">+</span> productItem<span class="token punctuation">.</span>quantity <span class="token operator">*</span> plusOrMinus<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>What if we need to handle now also:</p> <ul> <li>Show the shopping cart status and show whether the shopping cart is open, confirmed or cancelled.</li> <li>not only show totals, but also a list of product items with their details.</li> </ul> <p>Our new read model would look like:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">ShoppingCartSummary</span> <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItemsCount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> totalAmount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> status<span class="token operator">:</span> <span class="token string">'Opened'</span> <span class="token operator">|</span> <span class="token string">'Confirmed'</span> <span class="token operator">|</span> <span class="token string">'Cancelled'</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ProductItem</span> <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> unitPrice<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And here we should ask ourselves the following questions:</p> <ul> <li>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?</li> <li>If the read model is the same, then are you fine with downtime where you clean the old data, and reprocess events?</li> <li>If it’s the new read model, do we need to backfill it with the old data?</li> </ul> <p>There’s no best practice here; we need to do the drill and be prepared for multiple options.</p> <h2 id="add-new-vs-update-existing" style="position:relative;"><a href="#add-new-vs-update-existing" aria-label="add new vs update existing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Add new vs update existing.</h2> <p>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.</p> <p>You could say that:</p> <blockquote> <p>Why add a new read model with similar data? Can’t we just do a subquery?</p> </blockquote> <p>Of course, you can. I wrote about that more in <a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">How to create projections of events for nested object structures?</a>. This can make sense if those read models will always evolve together.</p> <p>If you need to run multiple <em>views</em> 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.</p> <p>You’re getting a smaller storage size, and potentially don’t need to remember multiple read models, but are doing it just one.</p> <p>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.</p> <p>Still, if those models are indeed the same, or we bet they’ll constantly evolve together, then it could be fine to reuse them.</p> <h2 id="in-place-update-bluegreen-rebuild-and-backfilling-data" style="position:relative;"><a href="#in-place-update-bluegreen-rebuild-and-backfilling-data" aria-label="in place update bluegreen rebuild and backfilling data permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>In place update, blue/green rebuild and backfilling data</h2> <p>Ok, I was already using <em>rebuilding</em> word multiple times. But how do we actually do it?</p> <p>If you’ve read articles on <a href="/en/checkpointing_message_processing/">checkpointing</a> or <a href="/en/lets_talk_about_positions_in_event_stores/">positions in event stores</a>, 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 <a href="/en/consumers_processors_in_emmett/">consumers and processors</a> work in <a href="https://github.com/event-driven-io/emmett">Emmett</a>. 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.</p> <p>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.</p> <p>And this is the moment we need to decide whether to do an in-place update or a blue/green rebuild.</p> <p>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.</p> <p>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.</p> <p>Typically, we need to spin up a background worker (e.g., <a href="/en/consumers_processors_in_emmett/">Emmett consumer with a projector plugged in</a>) 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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <h2 id="concurrency-issues-while-rebuilding-read-models" style="position:relative;"><a href="#concurrency-issues-while-rebuilding-read-models" aria-label="concurrency issues while rebuilding read models permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Concurrency issues while (re)building read models</h2> <p>To make it harder, eventual consistency is not the only challenge we’ll face. We also need to deal with concurrency and parallel processing.</p> <p>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.</p> <p>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?</p> <p>Then we’re doomed, or at least the consistency of our read model data.</p> <p>How do we solve it? Let me explain my plan for <a href="https://github.com/event-driven-io/emmett">Emmett</a>.</p> <h2 id="distributed-locking-and-postgresql-advisory-locks" style="position:relative;"><a href="#distributed-locking-and-postgresql-advisory-locks" aria-label="distributed locking and postgresql advisory locks permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Distributed Locking and PostgreSQL advisory locks</h2> <p>I encourage you to check my other article: <a href="https://www.architecture-weekly.com/p/distributed-locking-a-practical-guide">Distributed Locking: A Practical Guide</a>. Yet, don’t worry, I won’t leave you with Read-The-Fucking-Manual type of answer.</p> <p>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.</p> <p>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 <a href="https://github.com/event-driven-io/emmett">Emmett</a>’s PostgreSQL projection handling.</p> <p>PostgreSQL gives us two options for coordination.</p> <p><strong>Row-level locks</strong> lock individual table rows. You do <em>SELECT … FOR UPDATE</em>, and anyone else trying to modify that row waits. The lock is tied to the specific row in a table.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">BEGIN</span><span class="token punctuation">;</span> <span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> some_table <span class="token keyword">WHERE</span> id <span class="token operator">=</span> <span class="token variable">@key</span> <span class="token keyword">FOR</span> <span class="token keyword">UPDATE</span><span class="token punctuation">;</span> <span class="token comment">-- make changes</span> <span class="token keyword">COMMIT</span><span class="token punctuation">;</span></code></pre></div> <p>Such locks are straightforward, as we explicitly state what we want to lock. We could define the following table for projections:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token constant">CREATE</span> <span class="token constant">TABLE</span> <span class="token constant">IF</span> <span class="token constant">NOT</span> <span class="token constant">EXISTS</span> <span class="token function">emt_projections</span><span class="token punctuation">(</span> version <span class="token constant">INT</span> <span class="token constant">NOT</span> <span class="token constant">NULL</span> <span class="token constant">DEFAULT</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name"><span class="token constant">VARCHAR</span></span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token constant">NOT</span> <span class="token constant">NULL</span><span class="token punctuation">,</span> name <span class="token constant">TEXT</span> <span class="token constant">NOT</span> <span class="token constant">NULL</span><span class="token punctuation">,</span> status <span class="token constant">TEXT</span> <span class="token constant">NOT</span> <span class="token constant">NULL</span><span class="token punctuation">,</span> <span class="token constant">PRIMARY</span> <span class="token constant">KEY</span> <span class="token punctuation">(</span>name<span class="token punctuation">,</span> version<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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.</p> <p><strong><a href="https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS">Advisory locks</a></strong> 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.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token comment">-- Shared lock: many can hold simultaneously</span> <span class="token keyword">SELECT</span> pg_try_advisory_xact_lock_shared<span class="token punctuation">(</span><span class="token number">12345</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">-- Exclusive lock: only one holder, blocks shared locks</span> <span class="token keyword">SELECT</span> pg_advisory_lock<span class="token punctuation">(</span><span class="token number">12345</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Those locks are either scoped to an open connection session or an opened transaction (with <em>xact</em> with name) and released automatically once they end.</p> <p>Shared locks allow multiple sessions to access the lock with the same value. Exclusive lock blocks both other exclusive locks and new shared locks.</p> <p><strong>This allows a design where shared locks are used for readers</strong>, and exclusive locks for writers and maps directly to our problem:</p> <ul> <li>Inline projections take <strong>shared</strong> locks as they run concurrently, and just need to check if there’s no async job updating these projections</li> <li>Rebuilds take <strong>exclusive</strong> locks - they block inlines and other instances of async processing.</li> </ul> <p>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.</p> <p>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 <a href="https://en.wikipedia.org/wiki/Hash_collision">hash collision</a> 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:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token comment">// shared for inline projections</span> <span class="token keyword">SELECT</span> pg_try_advisory_xact_lock_shared<span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token string">'x'</span> <span class="token operator">||</span> substr<span class="token punctuation">(</span>md5<span class="token punctuation">(</span>?<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">)</span>::<span class="token keyword">bit</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span>::<span class="token keyword">bigint</span> <span class="token punctuation">)</span> <span class="token keyword">AS</span> acquired<span class="token punctuation">;</span> <span class="token comment">// exclusive for inline projections</span> <span class="token keyword">SELECT</span> pg_try_advisory_xact_lock<span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token string">'x'</span> <span class="token operator">||</span> substr<span class="token punctuation">(</span>md5<span class="token punctuation">(</span>?<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">)</span>::<span class="token keyword">bit</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span>::<span class="token keyword">bigint</span> <span class="token punctuation">)</span> <span class="token keyword">AS</span> acquired<span class="token punctuation">;</span></code></pre></div> <p>Where as query param, we’d pass the joined projection name and its version.</p> <p>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.</p> <p>An exclusive lock can be held only when there’s no single inline projection being applied at the moment.</p> <p>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.</p> <p>Is that all? Well, not quite.</p> <h2 id="why-advisory-locks-alone-arent-enough" style="position:relative;"><a href="#why-advisory-locks-alone-arent-enough" aria-label="why advisory locks alone arent enough permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why advisory locks alone aren’t enough</h2> <p>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:</p> <ol> <li>We have 1000 events in our event store.</li> <li>Rebuild starts, acquires exclusive lock.</li> <li>It truncates the projection and processes events 1-500.</li> <li>Connection dies (network blip, out of memory kill, whatever).</li> <li>Lock releases automatically.</li> <li>Projection contains data only for events 1-500.</li> <li>Inlines see no lock, start processing.</li> <li>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).</li> </ol> <p>Advisory locks can’t persist across connection failures. We need to add something that does.</p> <h2 id="the-hybrid-locking-approach" style="position:relative;"><a href="#the-hybrid-locking-approach" aria-label="the hybrid locking approach permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The hybrid-locking approach</h2> <p>Emmett already maintains tables for tracking processors and projections. The relevant ones:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> emt_projections<span class="token punctuation">(</span> version <span class="token keyword">INT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> name <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">partition</span> <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'emt:default'</span><span class="token punctuation">,</span> kind <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">status</span> <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> definition JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'{}'</span>::jsonb<span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span>name<span class="token punctuation">,</span> <span class="token keyword">partition</span><span class="token punctuation">,</span> version<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> LIST <span class="token punctuation">(</span><span class="token keyword">partition</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> emt_processors<span class="token punctuation">(</span> last_processed_transaction_id XID8 <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> version <span class="token keyword">INT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token number">1</span><span class="token punctuation">,</span> processor_id <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">partition</span> <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'emt:default'</span><span class="token punctuation">,</span> <span class="token keyword">status</span> <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'stopped'</span><span class="token punctuation">,</span> last_processed_checkpoint <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> processor_instance_id <span class="token keyword">TEXT</span> <span class="token keyword">DEFAULT</span> <span class="token string">'emt:unknown'</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span>processor_id<span class="token punctuation">,</span> <span class="token keyword">partition</span><span class="token punctuation">,</span> version<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> LIST <span class="token punctuation">(</span><span class="token keyword">partition</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The <em>status</em> column in the projections table can do what advisory locks can’t: persist status between connection crashes. Even if the connection dies, <em>status = ‘rebuilding’</em> stays in the table. Inlines could check this and skip processing.</p> <p>The processor’s table tracks checkpoint progress. When a rebuild runs, it updates <em>last_processed_checkpoint</em> as it goes. If it crashes and restarts, it can resume from where it left off rather than starting over.</p> <p>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.</p> <p>Having that, the query checking if the inline projection should be processed or skipped could look as follows:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">WITH</span> lock_check <span class="token keyword">AS</span> <span class="token punctuation">(</span> <span class="token keyword">SELECT</span> pg_try_advisory_xact_lock_shared<span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token string">'x'</span> <span class="token operator">||</span> substr<span class="token punctuation">(</span>md5<span class="token punctuation">(</span>$<span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">)</span>::<span class="token keyword">bit</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span>::<span class="token keyword">bigint</span> <span class="token punctuation">)</span> <span class="token keyword">AS</span> acquired <span class="token punctuation">)</span><span class="token punctuation">,</span> status_check <span class="token keyword">AS</span> <span class="token punctuation">(</span> <span class="token keyword">SELECT</span> <span class="token keyword">status</span> <span class="token operator">=</span> <span class="token string">'active'</span> <span class="token keyword">AS</span> is_active <span class="token keyword">FROM</span> emt_projections <span class="token keyword">WHERE</span> <span class="token keyword">partition</span> <span class="token operator">=</span> $<span class="token number">2</span> <span class="token operator">AND</span> name <span class="token operator">=</span> $<span class="token number">3</span> <span class="token operator">AND</span> version <span class="token operator">=</span> $<span class="token number">4</span> <span class="token punctuation">)</span> <span class="token keyword">SELECT</span> <span class="token keyword">COALESCE</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">SELECT</span> acquired <span class="token keyword">FROM</span> lock_check<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token keyword">AS</span> acquired<span class="token punctuation">,</span> <span class="token keyword">COALESCE</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">SELECT</span> is_active <span class="token keyword">FROM</span> status_check<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token keyword">AS</span> is_active<span class="token punctuation">;</span></code></pre></div> <p>Two checks, both must pass:</p> <ol> <li>Can we get a shared advisory lock? Fails if a rebuild holds exclusive.</li> <li>Is the projection status ‘active’? Fails if it’s ‘rebuilding’.</li> </ol> <p>Then for async (re)building worker:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">WITH</span> lock_check <span class="token keyword">AS</span> <span class="token punctuation">(</span> <span class="token keyword">SELECT</span> pg_try_advisory_lock<span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token string">'x'</span> <span class="token operator">||</span> substr<span class="token punctuation">(</span>md5<span class="token punctuation">(</span>$<span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">16</span><span class="token punctuation">)</span><span class="token punctuation">)</span>::<span class="token keyword">bit</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span>::<span class="token keyword">bigint</span> <span class="token punctuation">)</span> <span class="token keyword">AS</span> acquired <span class="token punctuation">)</span><span class="token punctuation">,</span> ownership_check <span class="token keyword">AS</span> <span class="token punctuation">(</span> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> emt_processors <span class="token punctuation">(</span> processor_id<span class="token punctuation">,</span> <span class="token keyword">partition</span><span class="token punctuation">,</span> version<span class="token punctuation">,</span> processor_instance_id<span class="token punctuation">,</span> <span class="token keyword">status</span><span class="token punctuation">,</span> last_processed_checkpoint<span class="token punctuation">,</span> last_processed_transaction_id <span class="token punctuation">)</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span>$<span class="token number">2</span><span class="token punctuation">,</span> $<span class="token number">3</span><span class="token punctuation">,</span> $<span class="token number">4</span><span class="token punctuation">,</span> $<span class="token number">5</span><span class="token punctuation">,</span> <span class="token string">'running'</span><span class="token punctuation">,</span> <span class="token string">'0'</span><span class="token punctuation">,</span> <span class="token string">'0'</span>::xid8<span class="token punctuation">)</span> <span class="token keyword">ON</span> CONFLICT <span class="token punctuation">(</span>processor_id<span class="token punctuation">,</span> <span class="token keyword">partition</span><span class="token punctuation">,</span> version<span class="token punctuation">)</span> <span class="token keyword">DO</span> <span class="token keyword">UPDATE</span> <span class="token keyword">SET</span> processor_instance_id <span class="token operator">=</span> $<span class="token number">5</span><span class="token punctuation">,</span> <span class="token keyword">status</span> <span class="token operator">=</span> <span class="token string">'running'</span> <span class="token keyword">WHERE</span> emt_processors<span class="token punctuation">.</span>processor_instance_id <span class="token operator">=</span> $<span class="token number">5</span> <span class="token comment">-- We already own it</span> <span class="token operator">OR</span> emt_processors<span class="token punctuation">.</span>processor_instance_id <span class="token operator">=</span> <span class="token string">'emt:unknown'</span> <span class="token comment">-- Unclaimed</span> <span class="token operator">OR</span> emt_processors<span class="token punctuation">.</span><span class="token keyword">status</span> <span class="token operator">=</span> <span class="token string">'stopped'</span> <span class="token comment">-- Previous instance finished or crashed</span> <span class="token keyword">RETURNING</span> last_processed_checkpoint <span class="token punctuation">)</span> <span class="token keyword">SELECT</span> <span class="token keyword">COALESCE</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">SELECT</span> acquired <span class="token keyword">FROM</span> lock_check<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token keyword">AS</span> acquired<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">SELECT</span> last_processed_checkpoint <span class="token keyword">FROM</span> ownership_check<span class="token punctuation">)</span> <span class="token keyword">AS</span> <span class="token keyword">checkpoint</span><span class="token punctuation">;</span></code></pre></div> <p>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</p> <p>The rebuild acquires the exclusive lock <em>before</em> updating the status. It waits for in-flight inlines (which hold shared locks) to finish.</p> <p>If the checkpoint is null, the UPDATE matched no rows, then another instance owns the processor and is actively running. Back off.</p> <p>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.</p> <p>If the rebuild crashes, the lock is released, but the status remains ‘rebuilding’. Inlines check status and skip.</p> <h2 id="the-scenarios-walkthrough" style="position:relative;"><a href="#the-scenarios-walkthrough" aria-label="the scenarios walkthrough permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The scenarios walkthrough</h2> <p>As a single good image speaks more than thousands of words, let me give you some diagrams to summarise how it works.</p> <p><strong>1. During inline projection while event appends:</strong></p> <ul> <li>Each inline projection grabs a shared lock before applying,</li> <li>Multiple inlines can run concurrently (shared locks are compatible),</li> <li>Projection updates happen normally.</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/dca429485d4697b503af71b7f8576b11/80631/2026-01-05-mermaid1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 87.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAACXBIWXMAAAsTAAALEwEAmpwYAAACOklEQVQ4y4VUiU7jQAzl//9vVwIWWprmPibn3J63stOwVLAQyXIyrp+f37h+AADSCVElxCnBdF68fG/EYaSUxPhZYkAVDNpgketFfOENVHASfxDAlcGAsBCufyr4OQqoHyPDCRgxYAKG4NBFizE6vDY1VLBookXrzAdAl+AHgusDttqAGFwRksenh48qb9BEh2ydUZgVud0wHwz3VvZ2IkV0XY9t0zDG4HzO8PvXEy6XHK+vb3h+PqGpW1hjsa0aZVkjxvhejLEejhcBjIS+VzDGQmsjwMMwiim1ez7jGFtVNpLzEeNLQOccQgjw3uN6LVHklSTXdQvvA0KIsNahqtp3ht8CKjWhKOp3q6pGzhmUwbnIqKafGVIkdN0greW3xOySI7+Wwo6/r1khmrZtjywrQPRty1GYcNWm6dCytT2GXmGeFil2GF/aNM0izX8B2XNLLPy6bpjnFcuyip/nRQA4xjfMOqth/B7w0JCTWHRm9/j4Ii2fT5kYx621UpD1/aHlXcODBY8GM2PNWDs+k5h0oL++lI+DzdVYO26NjcGY0el0wdv5inGcREu2cZylyMHwbrDJJsQhIQwEXTtZDKFPiJo+/fU0RVkGpTc4zQql07h6jSn4fy3HJYFGIMyE7KmEnwhxBPx0G1q6LQfsy6Hl5UAez1WJwRvU0aK5Ww4mwXckC8K2QbzvIsIWP62vjQJyp1E4LUuBGV7sCuXtoaGkiBY82MIm3t6xr6yEdCd8JEIguvNI+6/+Am78J43sg1ZxAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid1" title="mermaid1" src="/static/dca429485d4697b503af71b7f8576b11/a331c/2026-01-05-mermaid1.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>2. When a rebuild starts:</strong></p> <ul> <li>Rebuild grabs an exclusive lock (waits for any in-flight inlines to finish),</li> <li>Marks projection as “rebuilding”,</li> <li>New inlines see the lock or status and skip,</li> <li>Rebuild processes all historical events,</li> <li>Marks the projection as “active” and releases the lock,</li> <li>Inlines resume.</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/94a56b07c4abbaaf770433de25df83e6/19f2d/2026-01-05-mermaid2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 73.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB9klEQVQ4y42Ui46cMAxF+f9vrFp1dwaGR4A8eCSxfVcOMN3taNVGggTHPrmxEypJApoES7cjGgJ5ASAQAURfAGZKeKQN9RpQpw0r5WLHPiPPNyz9G3h+h6QVlQLzxJhrj21IIM8nUL4C44p7cGjS+glokaY7pvon8nQBNZAFS1hLf0E+A7UnEfRmxKPt0TQt2nZAU7cww4DgHMBU/CrBERTCgmt8wZ7QwwxnPZgIOefiz8xIOcNaf8bhUKgBqvBfzVoHYya0jx5dN6Btj17tl5Dq2lbwC4iorBpjhHMBznnseyw2nes7A+9CUeR9KKDL7wWoxstpHOeyuj7jOJW5aZqfSv5uy7J+D1SYGUYMw4i+N5jGudh0ByXPp//V9FvtL0DdhgZt214Add2ivj8wT7bYDpW2+OZMJQVE/L1CXYVKBY88XhXWIDorq6p1F3pcHo+uFGRdt1L9r8D/rLKmQKusMB1r/ye3h4DqOmTLegHl9RyebR4tKOWS46bpMFuPlBn+FKOelZCAvSD0K7Jj8CYvV2/hjJEifhuDm7cwzpXjE6Yeob/Btu+QpYfkiEqiIBvBfPfYugSyUm7MZ6DCmvMu3/OGVej5c0jjG8b7D2TzCxLDkUPeGRIB3gWc+eUuZ2YEykVpyAnEhw9TgsQFyBs4LhAmfAAnspIyLynbJAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid2" title="mermaid2" src="/static/94a56b07c4abbaaf770433de25df83e6/a331c/2026-01-05-mermaid2.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>3. If rebuild crashes:</strong></p> <ul> <li>Lock releases automatically</li> <li>Status stays “rebuilding”</li> <li>Inlines keep skipping until another rebuild completes</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2a542e63e9b9755604542de17e69f2c7/0bcb8/2026-01-05-mermaid3.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 98%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC4UlEQVQ4y5WUy47jNhBF+///IQgmAQZOPiFZBckiq0FWkwHS3X5b1pOiLfEp8gSk7G67jSxSQAEEhXt161axnmKITH3EdQHbenwbCSriCZTeUDhNYRV7pxkmR4rgFFHucGKHFzuC3BG9yd+eoo8ECUOtabY9vg+E84SJnnrS1HpgLzuqSdM7nUFMBlRNV2yQ5S6fmfyFMEaCiejeontDUBDDjBuBWnsa7ZEBZsiEOPxC/fyZbrmgfVnk86n8FaKdCVMYa1DaQhTY8Rtu/IdJPaP7v1H9V4J+xqkX7Pkv+sOCc/M7ovwt59D9wdD9+YHQGIwJBP2Vavk97fYz5fJHipdPc75+4vj6A5tv3yGTmiwC3GxrjngteSa0qFGjtaIXDV1bo0aJVmeMHlIrmIv2EF2GK6UwRudzTD7F+E5oraVtBUJI1us9q+WOoqg5HhukHObuxozhAkErnYVkdTHmvCMUosc5R9O0dJ1gGEaUSqrNHeiK+UjIbcnWWKQ8IeWZ4lCy3x/p+xNN03E6ne9A/4swK+36t/LTXVL5SBjRdx5+JLQ2q+lawXZ7YLvZU5Z1Jh1HdVfyNYwB6947/ODhteSkLmUqtalvS54nfrJVnsdz+4Wh+5LPwYt7hcmLq/m3Ya3LP3vXQR76er2geP2Zw8tPVKsFXm8eCW+9ejNem9ztXshsSfK36/qsOt177y+YDyX/F2G6Px6r3Pnl64bVastquWW92nE81ncjlSq4ecsWpR8JU/eTunEYs59JZco0Ur0834/Nm8IIVjnMaPMLuxKmFgzaULUdVSvYH44URcnhcKSpaqQQWD2vp4zJHk7zPtSNYyg1k4AwRlwMVGnBmpGN7Dk4xclf1DiVl+q5XKKaDfF0eFywY2MQ+xNeBMIQMBfCyiiK4UQ5GaS7Eo6ZRBRLZLkmnvaEy/J9yo/ehrxkcTDpQFKdQk2eYfKMYeLsHdNlDtM8BjuCV5DU2vHtpfwLgMwMITX4ZYcAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid3" title="mermaid3" src="/static/2a542e63e9b9755604542de17e69f2c7/a331c/2026-01-05-mermaid3.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>4. Multiple async processors:</strong></p> <ul> <li>Each processor tries to acquire the exclusive lock,</li> <li>First one wins, others wait or skip,</li> <li>Guarantees that only one processor handles a projection at a time.</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e008a6d034bc882fe15fec2c63ec1410/faa17/2026-01-05-mermaid4.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 102.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAVCAYAAABG1c6oAAAACXBIWXMAAAsTAAALEwEAmpwYAAAC8klEQVQ4y52V7XLjJhiFc/8XsD86vZy9gf7ozHY3cRzLtj5BgAT6Ap4t2E6cppvOlJl3ACGOzst5dXiAiFeRud1wzcLcbWwywBbp/MJxGims4egGitkyhQB+JZoz0ZSs/RGu4wg8xAQoQRw05a6lKxRzuxKmQB1mDkryvSrZNQ0HZxiCh9URzYnVlPTnHdga3x+IER4A/BgIQ8SJGVyax0QcEzaaxXEeTY5qsqwxkBajkzBJ5r7CDy3ByivDBHttQkqGcWS0lnG06F5TnWt2P57ZPe6pzzWq13ltdDOyN9SNzHszSowXhjdQKRTOTSzLyjwvrOuG0ob9vuBQnJFSva4ty5LflbLnhpEZ3gO2rcBaRwiBbfOs64oxI2XZUJUNwzDmj2zblnvvPUoZ7jHeM5SKcbB5YwJO88PhxNPjnscfz4hOMk1zZpZYJrBE4peASunM6sYg9alZa5Giz5tPp5Kqajifa/pev0v5A6DRA//WrJ1p2x4hVAar65am7jJY9xlDrc3lcHNADGsqKgYjMLrDqI6yrKirlqbpsjCfnuEbYLiUgftKs/tC/fQbxbcv2Op38M0VIFxF0Z+kbP6Rcmix6k9E9UeO1X6HOL97JVXBLasPgEL0GTSpnPtxpqp6vv31wtPTCaUmpmnJFZDUTqK0zX+onJS9RVI8/QPOWYSQtE37TmX1/1V2dJ3MGZRlTVW1uYRSnf66DmMSZbhTORJemZuc3mhdBkgF3vcqZ6GTyrc99+aQpkprLo9vP/uFoRstXdPlaOo2M0zGMM0rQuqP5hBczLbl5MW+go0QYPQbGk8zWfaiy1F3EtkJVFUgzy+5Z1LEybwZbOhBFppq1yGOmqXzxDnShIWXZLBlyb5rOS2WKTNYwFVE26KrZ3ANQRVvBrv1AVcv2HrK18DSbcQ10vqZwg0cBk0xGl6cwXoP24LXRzZ1YhYFQZ/w6nRnX+GSYn4SrvM0jBGfxEnXBJfx65kHTwwbRE/0GzHdNX8D/gSeflwHh/jWIgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="mermaid4" title="mermaid4" src="/static/e008a6d034bc882fe15fec2c63ec1410/a331c/2026-01-05-mermaid4.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>5. When you add a new projection:</strong></p> <ul> <li>No status row exists yet,</li> <li>Inlines skip automatically,</li> <li>First rebuild creates the row and backfills data.</li> </ul> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>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.</p> <p>I hope this walkthrough covers both the conceptual and practical aspects of handling projection rebuilds.</p> <p>We used PostgreSQL and Advisory Locks, as PostgreSQL is cool and is a driving force in <a href="https://github.com/event-driven-io/emmett">Emmett</a>. But all the same principles apply to other tools and storage (with their specifics).</p> <p>I explained why advisory locks and status columns complement each other:</p> <ul> <li><strong>Advisory locks</strong> 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)</li> <li><strong>Status column</strong> handles crash recovery (persists across connection failures) and new projection bootstrapping (inlines skip until first rebuild completes)</li> </ul> <p>Neither alone is sufficient. Together, they provide the guarantees we need without external infrastructure. Just PostgreSQL doing what PostgreSQL does.</p> <p>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.</p> <p>I hope this article also shows you how to use distributed locking in practice.</p> <p><strong>Please tell me your thoughts and concerns, especially if you see any blind spots in this design!</strong> You can do that in our <a href="https://discord.gg/fTpqUTMmVa">Emmett Discord</a>, come on in, we have a nice community!</p> <p><strong>If you’re dealing with such issues, I’m happy to help you through consulting or mentoring. <a href="mailto:[email protected]">Contact me</a> and we’ll find a way to unblock you!</strong></p> <p>Or check also other related resources:</p> <ul> <li><a href="https://github.com/event-driven-io/emmett/pull/286">Emmett’s Pull Request implementing described approach</a></li> <li><a href="/en/projections_and_read_models_in_event_driven_architecture/">Guide to Projections and Read Models in Event-Driven Architecture</a>,</li> <li><a href="https://www.architecture-weekly.com/p/distributed-locking-a-practical-guide">Distributed Locking: A Practical Guide</a>,</li> <li><a href="/en/consumers_processors_in_emmett/">Consumers, projectors, reactors and all that messaging jazz in Emmett</a>,</li> <li><a href="/en/how_to_scale_projections_in_the_event_driven_systems/">How to scale projections in the event-driven systems?</a>,</li> <li><a href="/en/checkpointing_message_processing/">Checkpointing the message processing</a>,</li> <li><a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Multi-tenancy and dynamic messaging workload distribution]]>https://event-driven.io/en/multitenant_and_dynamic_message_handling/https://event-driven.io/en/multitenant_and_dynamic_message_handling/<p>There are several reasons why I’m blogging.</p> <p>The first one is that I forget, and writing helps me to remember and organise my findings.</p> <p>The other is that I like to share my journey with the intention of sparing you, my dear reader, some of my struggles.</p> <p>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.</p> <p>After the article about <a href="/en/consumers_processors_in_emmett/">consumer, processors and all that messaging jazz</a>, there was a great discussion on <a href="https://discord.gg/fTpqUTMmVa">Emmett Discord</a>, there were a lot of interesting threads there, but the one that inspired what you read today came from the <a href="https://ismaelcelis.com/">Ismael Celis</a> question. Ismael was curious about:</p> <h2 id="distributing-the-workload-dynamically-between-processors" style="position:relative;"><a href="#distributing-the-workload-dynamically-between-processors" aria-label="distributing the workload dynamically between processors permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Distributing the workload dynamically between processors</h2> <p>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.</p> <p>Why would we want to distribute the workload dynamically? What does that even mean?</p> <p>Let’s start from the classical approach. We can define projections that will build read models based on the upcoming events. In <a href="https://github.com/event-driven-io/emmett">Emmett</a>, it can look like that when <a href="https://event-driven-io.github.io/emmett/getting-started.html#read-models">using Pongo as the storage tool</a>:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> cartsSummaryProjection <span class="token operator">=</span> <span class="token function">pongoSingleStreamProjection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> collectionName<span class="token operator">:</span> shoppingCartsSummaryCollectionName<span class="token punctuation">,</span> <span class="token function-variable function">getDocumentId</span><span class="token operator">:</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=></span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function-variable function">initialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'pending'</span><span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre></div> <p>This means that when the projection handles only <em>ProductItemAdded</em> and <em>ShoppingCartConfirmed</em> event types. It’ll insert or update rows in the table based on the shopping cart id from the event data.</p> <p>We can plug this projection into a projector (the specific type of processor responsible for running projections).</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> cartsSummaryProjector <span class="token operator">=</span> <span class="token function">postgreSQLProjector</span><span class="token punctuation">(</span><span class="token punctuation">{</span> processorId<span class="token operator">:</span> <span class="token string">'shoppingCartSummary'</span><span class="token punctuation">,</span> projection<span class="token operator">:</span> cartsSummaryProjection<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And plug it into the consumer:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> consumer <span class="token operator">=</span> <span class="token function">postgreSQLEventStoreConsumer</span><span class="token punctuation">(</span><span class="token punctuation">{</span> connectionString<span class="token punctuation">,</span> processors<span class="token operator">:</span> <span class="token punctuation">[</span> cartsSummaryProjector <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Now, consumers will poll the PostgreSQL event store and pipe filtered events by type into our projector.</p> <p>That’s simple, we know the filtering criteria, as consumers will know which event types their processor(s) can handle.</p> <p>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.</p> <h2 id="what-options-do-we-have-for-such-a-multi-tenant-setup" style="position:relative;"><a href="#what-options-do-we-have-for-such-a-multi-tenant-setup" aria-label="what options do we have for such a multi tenant setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What options do we have for such a multi-tenant setup?</h2> <p>The simplest option would be to add a custom filter that allows filtering events on the consumer by the tenant.</p> <p><a href="https://github.com/event-driven-io/emmett">Emmett</a> 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.</p> <p>We could even wrap it into a dedicated function.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">tenantedConsumer</span><span class="token punctuation">(</span> connectionString<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> tenant<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> processors<span class="token operator">:</span> MessageProcessor<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">postgreSQLEventStoreConsumer</span><span class="token punctuation">(</span><span class="token punctuation">{</span> connectionString<span class="token punctuation">,</span> processors<span class="token punctuation">,</span> <span class="token function-variable function">filterBy</span><span class="token operator">:</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=></span> event<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>tenant <span class="token operator">===</span> tenant<span class="token punctuation">,</span> projection<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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).</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> consumer <span class="token operator">=</span> <span class="token function">tenantedConsumer</span><span class="token punctuation">(</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">PG_CONNECTION_STRING</span><span class="token punctuation">,</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">TENANT_ID</span><span class="token punctuation">,</span> <span class="token punctuation">[</span> cartsSummaryProjector <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>Still, logically, they would look more or less like that. Either you’re scaling horizontally and sharding physically, tenants</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2174a833e6942562c7139d9698f9c168/064e6/2025-12-15-containers.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 80%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAQCAYAAAAWGF8bAAAACXBIWXMAAC4jAAAuIwF4pT92AAACt0lEQVQ4y42Uy2sUWRTG+88QZi9u3M/OzSwU3cmAK2UGFyIiiOBORBR0Y9CMzogvNKgxZrANmPjAiGKiEXsGNKZV4ittxySm0t31vs/6ya2yTUciWPBx7rn31LlfnXO+KnnzTer1GRa8FkpapDA5RGrAZjx5PsuZcpWeG684N/CS2qcQqzPE17jvUUoThYNzjOYbpMhwz57uMVasPc/qTX38sr6HwZFavi9ltiS+jZJLZE3haJV9g5JFwuEndQ73PKer9wUHz1R4W/PzfXfeGd9Gqc3MOZ1J2+v80QHRuwpZ4kO2GPtDhsslyiEtOoPgvzIzpzahZiZR1p3bJaw6CS3L0NmiDJnrC/pTFX/sEjbwsBbMdww7GS/LMIwUH6c9Gs2UIDLMtwxvak0+1BsEoSKKLWFsCCODEPbHDLUqWLngBd9iDBztnWDDriE277vLup03uFeZRWpohhmtKCNNzc8ktDSCzNWfvScrrNzYy69/lln1+xUGR6fRBprBTyUsNqNY44eaKDbUZxOevfaojNepvm0x5yVEic0vDfJP7kzYMTbtWRSpJgpT4igliQVpLFiY99BC0VpoMPV+iobXysXglLW001BSspBMqxnmB84KofN1FEnCICbwozzG9yMajZDPcw1EKpGpyufSkSmak1FylK02PJ5I6LouOTGoOFKWPKpq4lhwbTTh1G3F2TuKi/cNng/v6z6nByb5u7/KX31V7j6dI0kLtiVXVHfN4FPDH92SnaclW44J/h3VOPb7+zRbjwu2n5Rs+0cw7cHY+GfWbLvJ5v0P+W3HLXZ3V5CykOPXGlpe1w3lx5qhiubaI83ElCFNNXf+11x+oLg6oukf0Xn3J2sBhy6Mc+TyBAfOPaN/uIYQHVpWKsNqCyzC+VJYcmlki9axdtJzvzBnTf5uljfIjc0X/4eruvZjxikAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="containers" title="containers" src="/static/2174a833e6942562c7139d9698f9c168/a331c/2025-12-15-containers.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>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.).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/361518c616e35d85958f56a6a8ab62c8/99811/2025-12-15-threads.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 61.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAC4jAAAuIwF4pT92AAACmklEQVQoz12TW0tUURiG5y90GV13UV0E3XQfXYRQUFCSdCI6XCRUhBklhQVlSHawxIpICxqLMoKQSrEy85SmVkIHG9Icx5nZzuzj2sfZT6w9Y4gLXtj722t9vOt99hdLKzozqSw5TSBcEG4YybCL0kWI4YBeercccHywXPkcYntE0qwgUmwu51LTPMyJawOcbByksr6Xd6NpbMdHMUPsxBBG1xVEYhhVQDJjcrrpE8ca+qi+Ocihiz28GkhSQDYtEPsxrbF6RxurtsdZW/GEFWWtXG/7gqqqTCZVlL44s01bSffG+ZsxGPk2He1due0R63Y/ZdmGe9S1jiFX3giIpfMOl1rHqL07wqWWMU7dGmJgQiEADBeMzAzi9xCWMocTgmqFXG2b4OztYWrvDEVuP4yl8cOSQ10Ekd0gJGri+qCZLpmcVZTmkTUho3pk5k0UVWDaHnnNRNVF5ExmupB5TAYpIRQbOaiGwLAL0UfHC/GCENOyUebz/J1VSGV1ZjMqybkcM6l58oYbwTAlOCEdWgFTWfj6x2X0l873JGS0IsFEGsYTDp9/GvyYhbwFwoPpOZ2JyTQj36b4NTVPNi+w3FLDrBZQ3eKy/5rF4UaT3Q02Tz/6ZLWQymaHAzcEh2/a7G2weTMaRI4qarrZWvWaijOdbDzyknhnIrq6akZQfA422pRfttnT4LD5vOB+l0cqF7Kz3qai3mbXFZstFyxeDBZIKRZryh+zfl87ZUc7WL6plboH40XKsqG8f8dIwKP3Hu39Pg+6Pb5OFTN83u8T7/F51ufz8K3HZCqM8pJ/Q9WNAc7dGeb41X56RhdRlhsk4UJJYYmaDLmwpC4nQtaXLi9YQlm3QRPhfy2M2eKa1MIh6URdJDmeC5T/Acv4U60Q8TyWAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="threads" title="threads" src="/static/361518c616e35d85958f56a6a8ab62c8/a331c/2025-12-15-threads.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>That’s nothing special for Emmett; other tools also do this, e.g., <a href="https://docs.spring.io/spring-kafka/reference/kafka/receiving-messages/message-listener-container.html#using-ConcurrentMessageListenerContainer">Spring Boot Kafka concurrent listeners</a>.</p> <p>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:</p> <ul> <li>internal tooling creator,</li> <li>user of other OSS tooling, to check how they solved it.</li> </ul> <p><strong>Separating tenants through sharding give you:</strong></p> <ul> <li>better option to scale and align the needs to the specific tenant workload,</li> <li>makes possible full separation in terms of networking, storage, etc.</li> <li>can be more costly.</li> </ul> <p><strong>Separating tenants through partitioning the load inside one box:</strong></p> <ul> <li>is usually cheaper,</li> <li>easier to manage,</li> <li>doesn’t give you a full separation and can fall into <a href="https://learn.microsoft.com/en-us/azure/architecture/antipatterns/noisy-neighbor/noisy-neighbor">Noisy Neighbour issue</a>.</li> </ul> <p>So there’s no golden rule to choose, it depends on your tooling, needs, etc.</p> <p>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.</p> <p>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.</p> <p><strong>Ok, but how do we know which messages to process where? We could use a <a href="https://en.wikipedia.org/wiki/Consistent_hashing">consistent hash</a>.</strong> I wrote about it in detail in <a href="https://www.architecture-weekly.com/p/understanding-kafkas-consumer-protocol">Understanding Kafka’s Consumer Protocol: A Deep Dive into How Consumers Talk to Brokers</a>.</p> <p>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.</p> <p>The pseudo code for distributing load to consumers could look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> partitionId <span class="token operator">=</span> message<span class="token punctuation">.</span>headers<span class="token punctuation">.</span>partitionId<span class="token punctuation">;</span> <span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token function">consistentHashFunction</span><span class="token punctuation">(</span>partitionId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> consumerId <span class="token operator">=</span> hash <span class="token operator">%</span> totalNumberOfConsumersWithinGroup<span class="token punctuation">;</span></code></pre></div> <p>In our case, we could use the tenant id as the partition id.</p> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> hash <span class="token operator">=</span> <span class="token function">consistentHashFunction</span><span class="token punctuation">(</span>message<span class="token punctuation">.</span>header<span class="token punctuation">.</span>recordId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> partition <span class="token operator">=</span> hash <span class="token operator">%</span> totalNumberOfPartitionsWithinTopic<span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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 <a href="https://raft.github.io/">Raft</a> and <a href="https://en.wikipedia.org/wiki/Paxos_(computer_science)">Paxos</a>.</p> <p><strong>Do I want to go into this area with Emmett?</strong> 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 <a href="https://cwiki.apache.org/confluence/display/KAFKA/KIP-500%3A+Replace+ZooKeeper+with+a+Self-Managed+Metadata+Quorum">implement such algorithms and specialise in that</a>. I’d prefer to make it easier to forward messages to them and let them do the work.</p> <p>What is definitely on the plate is the second option, which allows partitioned producers. You could define it as such:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> cartsSummaryProjector <span class="token operator">=</span> <span class="token function">postgreSQLProjector</span><span class="token punctuation">(</span><span class="token punctuation">{</span> processorId<span class="token operator">:</span> <span class="token string">'shoppingCartSummary'</span><span class="token punctuation">,</span> projection<span class="token operator">:</span> cartsSummaryProjection<span class="token punctuation">,</span> <span class="token function-variable function">partitionBy</span><span class="token operator">:</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=></span> event<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>tenant<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then the consumer would poll messages from all tenants, forward them to projectors, and the projectors would internally spin up <a href="https://nodejs.org/api/worker_threads.html">worker threads</a> per tenant or group them by consistent hashing.</p> <p>If we add to that distributed locking or <a href="/en/checkpointing_message_processing/">detecting conflicting checkpointing</a> 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.</p> <p>Keeping in mind the flexibility in which you can group (or not) projectors within consumers, you can define your own topologies.</p> <p>What are your thoughts? How do you deal with such cases? Would you like me to expand more on some cases?</p> <p>Or maybe you’d like to help me and <a href="https://github.com/sponsors/event-driven-io">sponsor my work in this area in Emmett</a>? Then your project could also benefit faster from it!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Checkpointing the message processing]]>https://event-driven.io/en/checkpointing_message_processing/https://event-driven.io/en/checkpointing_message_processing/<p>Let’s start by asking you two questions.</p> <ol> <li><strong>What <a href="https://en.wikipedia.org/wiki/Superfrog">Super Frog</a> has to do with messaging?</strong></li> <li><strong>When was the last time you wrote if statements in SQL?</strong> If it’s been a long time, have you at least seen them? If not, (don’t) worry, you’ll see them today.</li> </ol> <p>Will it be a post about weird SQL usage? Not necessarily.</p> <p><strong>We’ll talk today about checkpointing our processing.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b8e8597f7cafd3370a8b92a5055e61a3/a331c/2025-12-08-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAACyUlEQVQ4y1WT2W/bRhDG9XfUlh/qpJZF8ZCd6IhiiaRILg9RV3TY1mEJkqnEsRUXSdEDBfpeoI/9e3/FrtogfRjM7MzsN99+g82lbYuuf0FkGwRXJbKF4GGdsl3EZHcJu7sO2TLh/n8max1V+zYv+3KRYyCaOrFrEdoGt/0rpt0GqV+hL2r0wxo9UaUn/vNVukFFeVULql/z8pwLWzqRUyZsGcSOhbjSqBkn1PU8Ve2ISvE7Fdf0E2V1Q/rjQ+7f+GB5qvqJBDR4W9HUcxPXQkhQ2yT1ymxvPT7chQdJHDnQJHJMOp5Fz7sgcU0c95zYNZXJmmJo2wWEYxA2deahpRq7/iV//bHj7z9/5mb4hv39GV/2GqlrMJ0UOJ8dU5rmmY+KdFyL/U7jt89FcvJyx7foBhfEtskmvaQflBWrxbsmi2GLkdTN1xjHFp7QqEzy9C5P6dROeX2dJ3B0er5OT2gHhuoptkFom4SOpajLRSl9XR17ZhHYJtFbC1tomOM8M+0lk/IP6NNjArdE4pQRTZOcaBl0vbIClVuW2kktJZjUN3xToDszDho2LeywiDY5YnF2xo15RuH6CN/VFYbS0L/S6LQthASQLGzzq/iJbeF459TcFyqWfUlgEk9OqfePed3N0xm/JHJK6p4klMvmAaupQ7YI8RtFRLOEHNJuFHHqBTUkti01RLLv+yaDWKMSv6AmvmcclUi+IZT79WnEbhnxnA0QrUt8+xWzdw7vl4LHdUI/eEW7cU7sGCSOrpa3nnqsBg77u5TVxMNrFIlsnahVIvdpm7KZCR43PT5uhzxsp/z0ccLzfZdfnkbMhi523aQXNkiDOtcDh9+fJ2R3EZ8fhvyYDXDqOolXZdhpkdvMAvVvt/OQ1XWb/SZltxQspwH3i4gvj7dk6ynZasT9eszT7pr9NmU7j1jfCPbZkA+bMdvViKf3c/4BiFPWz02N/WsAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2025 12 08 cover" title="2025 12 08 cover" src="/static/b8e8597f7cafd3370a8b92a5055e61a3/a331c/2025-12-08-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>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.</p> <p>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?</p> <p>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.</p> <p><strong>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.</strong> 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.</p> <p>Surprisingly, this parallel also matches the recovery from a business process failure.</p> <p>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.</p> <p>We’re using <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox pattern</a> 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.</p> <p>Now we’re on the receiving end, so where we were in the previous article with the explanation of <a href="/en/consumers_processors_in_emmett/">Consumers, Processors and all that jazz</a>.</p> <p>Let’s say that we’re using PostgreSQL and our Outbox structure looks as explained in the <a href="/en/ordering_in_postgres_outbox/">other article</a>:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> outbox<span class="token punctuation">(</span> <span class="token comment">-- the autoincremented position of the message to respect the order</span> position <span class="token keyword">BIGINT</span> GENERATED <span class="token keyword">BY</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">AS</span> <span class="token keyword">IDENTITY</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token comment">-- used to detect gaps in numbering</span> transaction_id xid8 <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- unique message id, which can be used for deduplication or idempotency</span> message_id <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- the message type, e.g. `TransactionRecorded`</span> message_type <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- serialised message data, e.g. to JSON</span> <span class="token keyword">data</span> JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- diagnostic information on when the message was scheduled</span> scheduled <span class="token keyword">TIMESTAMP</span> <span class="token keyword">WITH</span> <span class="token keyword">TIME</span> ZONE <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">default</span> <span class="token punctuation">(</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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 <a href="/en/ordering_in_postgres_outbox/">here for reasoning</a>).</p> <p>Now we can be polling it with the query like:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> position<span class="token punctuation">,</span> message_id<span class="token punctuation">,</span> message_type<span class="token punctuation">,</span> <span class="token keyword">data</span> <span class="token keyword">FROM</span> outbox <span class="token keyword">WHERE</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span>transaction_id <span class="token operator">=</span> last_processed_transaction_id <span class="token operator">AND</span> position <span class="token operator">></span> last_processed_position<span class="token punctuation">)</span> <span class="token operator">OR</span> <span class="token punctuation">(</span>transaction_id <span class="token operator">></span> last_processed_transaction_id<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">AND</span> transaction_id <span class="token operator">&lt;</span> pg_snapshot_xmin<span class="token punctuation">(</span>pg_current_snapshot<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">ORDER</span> <span class="token keyword">BY</span> transaction_id <span class="token keyword">ASC</span><span class="token punctuation">,</span> position <span class="token keyword">ASC</span> <span class="token keyword">LIMIT</span> <span class="token number">100</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>That’s also the place where we’re back to our checkpointing. How do we know the last processed position?</p> <p>By default, that’s simple, we could either say:</p> <ul> <li><strong>start from the beginning</strong> and use -1 or some other hardcoded position,</li> <li><strong>start from the end</strong> and use the position of the last message in the table plus one.</li> </ul> <p>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.</p> <p>Still, the reality is just like in the old games, we’d like to start somewhere in the middle, precisely where we left off.</p> <p>We need one more table for storing our checkpoints. It can look as follows:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> processor_checkpoints <span class="token punctuation">(</span> <span class="token comment">-- subscription name</span> processor_id <span class="token keyword">TEXT</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token comment">-- information about the position of the last processed message</span> last_processed_position <span class="token keyword">INTEGER</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- used to detect gaps in numbering</span> last_processed_transaction_id xid8 <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Simple stuff: the processor ID should be unique and reflect the processor’s logical name and the last processed position(s).</p> <p>If that looks simple, then let me follow up with the next set of potentially simple questions:</p> <ul> <li>How to store it?</li> <li>When to store it?</li> </ul> <p>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.</p> <p>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:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token operator">OR</span> <span class="token keyword">REPLACE</span> <span class="token keyword">FUNCTION</span> store_processor_checkpoint<span class="token punctuation">(</span> p_processor_id <span class="token keyword">TEXT</span><span class="token punctuation">,</span> p_position <span class="token keyword">BIGINT</span><span class="token punctuation">,</span> p_expected_position <span class="token keyword">BIGINT</span><span class="token punctuation">,</span> p_transaction_id xid8 <span class="token punctuation">)</span> <span class="token keyword">RETURNS</span> <span class="token keyword">INT</span> <span class="token keyword">AS</span> $$ <span class="token keyword">DECLARE</span> current_position <span class="token keyword">BIGINT</span><span class="token punctuation">;</span> <span class="token keyword">BEGIN</span> <span class="token comment">-- Handle the case when p_check_position is provided</span> <span class="token keyword">IF</span> p_expected_position <span class="token operator">IS</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">THEN</span> <span class="token comment">-- Try to update if the position matches p_check_position</span> <span class="token keyword">UPDATE</span> processor_checkpoints <span class="token keyword">SET</span> <span class="token string">"last_processed_position"</span> <span class="token operator">=</span> p_position<span class="token punctuation">,</span> <span class="token string">"last_processed_transaction_id"</span> <span class="token operator">=</span> p_transaction_id <span class="token keyword">WHERE</span> <span class="token string">"processor_id"</span> <span class="token operator">=</span> p_processor_id <span class="token operator">AND</span> <span class="token string">"last_processed_position"</span> <span class="token operator">=</span> p_check_position<span class="token punctuation">;</span> <span class="token keyword">IF</span> FOUND <span class="token keyword">THEN</span> <span class="token keyword">RETURN</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">-- Successfully updated</span> <span class="token keyword">END</span> <span class="token keyword">IF</span><span class="token punctuation">;</span> <span class="token comment">-- Retrieve the current position</span> <span class="token keyword">SELECT</span> <span class="token string">"last_processed_position"</span> <span class="token keyword">INTO</span> current_position <span class="token keyword">FROM</span> processor_checkpoints <span class="token keyword">WHERE</span> <span class="token string">"processor_id"</span> <span class="token operator">=</span> p_processor_id<span class="token punctuation">;</span> <span class="token comment">-- Return appropriate codes based on current position</span> <span class="token keyword">IF</span> current_position <span class="token operator">=</span> p_position <span class="token keyword">THEN</span> <span class="token keyword">RETURN</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">-- Idempotent check: position already set</span> ELSIF current_position <span class="token operator">></span> p_expected_position <span class="token keyword">THEN</span> <span class="token keyword">RETURN</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment">-- Failure: current position is greater</span> <span class="token keyword">ELSE</span> <span class="token keyword">RETURN</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment">-- Default failure case for mismatched positions</span> <span class="token keyword">END</span> <span class="token keyword">IF</span><span class="token punctuation">;</span> <span class="token keyword">END</span> <span class="token keyword">IF</span><span class="token punctuation">;</span> <span class="token comment">-- Handle the case when p_check_position is NULL: Insert if not exists</span> <span class="token keyword">BEGIN</span> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> processor_checkpoints <span class="token punctuation">(</span><span class="token string">"processor_id"</span><span class="token punctuation">,</span> <span class="token string">"last_processed_position"</span><span class="token punctuation">,</span> <span class="token string">"last_processed_transaction_id"</span><span class="token punctuation">)</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span>p_processor_id<span class="token punctuation">,</span> p_position<span class="token punctuation">,</span> p_transaction_id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">RETURN</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">-- Successfully inserted</span> EXCEPTION <span class="token keyword">WHEN</span> unique_violation <span class="token keyword">THEN</span> <span class="token comment">-- If insertion failed, it means the row already exists</span> <span class="token keyword">SELECT</span> <span class="token string">"last_processed_position"</span> <span class="token keyword">INTO</span> current_position <span class="token keyword">FROM</span> processor_checkpoints <span class="token keyword">WHERE</span> <span class="token string">"processor_id"</span> <span class="token operator">=</span> p_processor_id<span class="token punctuation">;</span> <span class="token keyword">IF</span> current_position <span class="token operator">=</span> p_position <span class="token keyword">THEN</span> <span class="token keyword">RETURN</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">-- Idempotent check: position already set</span> ELSIF current_position <span class="token operator">></span> p_expected_position <span class="token keyword">THEN</span> <span class="token keyword">RETURN</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token comment">-- Insertion failed, row already exists with a greater position</span> <span class="token keyword">ELSE</span> <span class="token keyword">RETURN</span> <span class="token number">3</span><span class="token punctuation">;</span> <span class="token comment">-- Default failure case for mismatched positions</span> <span class="token keyword">END</span> <span class="token keyword">IF</span><span class="token punctuation">;</span> <span class="token keyword">END</span><span class="token punctuation">;</span> <span class="token keyword">END</span><span class="token punctuation">;</span> $$ <span class="token keyword">LANGUAGE</span> plpgsql<span class="token punctuation">;</span></code></pre></div> <p>Oooh, even by copy and pasting, I’m already tired; there’s a fair reason why we’re not doing that too often nowadays.</p> <p>Let me untangle that for you:</p> <ol> <li>We’re trying to update the existing position or insert it if we’re storing it for the first time.</li> <li>If all went fine, we’re returning 1 as a result to denote complete success.</li> <li>If we saw that the checkpoint in the database had the same value, we’re returning 0.</li> <li>If we saw that the checkpoint is different from what was expected and further away from it, then we’re returning 2.</li> <li>Otherwise, we’re returning 3, which means that the checkpoint is different from the expected and older.</li> </ol> <p>Essentially, by passing the expected position, we can detect whether we:</p> <ul> <li>already handled the specific position,</li> <li>have some competing instance of our processor handling our data.</li> </ul> <p>That’s why we’re doing this fancy dance with IF statements and a stored procedure.</p> <p>Detection assumes that we have a global ordering processing guarantee (thus, tricky bits with transaction ID).</p> <p><strong>It also shows why global ordering is useful.</strong></p> <p>By detecting that we’ve already handled a specific position, we can skip processing handling idempotency on the processor level.</p> <p>By detecting that there’s another processor with the same id processing messages, we can make it more resilient and detect the <em>noisy neighbour</em> issue.</p> <p>How would that look in the code?</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">handleBatch</span><span class="token punctuation">(</span>messageBatch<span class="token operator">:</span> RecordedMessage<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> context<span class="token operator">:</span> ProcessorContext<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>BatchHandlingResult<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> checkpoint <span class="token punctuation">}</span> <span class="token operator">=</span> messageBatch<span class="token punctuation">[</span>messageBatch<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">.</span>metadata<span class="token punctuation">;</span> <span class="token keyword">return</span> context<span class="token punctuation">.</span>pool<span class="token punctuation">.</span><span class="token function">withTransaction</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>transaction<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> message <span class="token keyword">of</span> messageBatch<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">onMessage</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// No error was thrown: proceed to store checkpoint of the last processed message</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">storeProcessorCheckpoint</span><span class="token punctuation">(</span>transaction<span class="token punctuation">.</span>execute<span class="token punctuation">,</span> <span class="token punctuation">{</span> processorId<span class="token operator">:</span> context<span class="token punctuation">.</span>processorId<span class="token punctuation">,</span> newCheckpoint<span class="token operator">:</span> checkpoint<span class="token punctuation">,</span> lastProcessedCheckpoint<span class="token operator">:</span> context<span class="token punctuation">.</span>lastProcessedCheckpoint<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>success<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> transaction<span class="token punctuation">.</span><span class="token function">commit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// no need to do here, either we already handled it</span> <span class="token comment">// or we have a mismatch of expected and existing checkpoints</span> <span class="token keyword">await</span> transaction<span class="token punctuation">.</span><span class="token function">rollback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">ProcessorContext</span> <span class="token operator">=</span> <span class="token punctuation">{</span> processorId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> lastProcessedCheckpoint<span class="token operator">:</span> bigint <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token function-variable function">onMessage</span><span class="token operator">:</span> <span class="token punctuation">(</span>message<span class="token operator">:</span> AnyMessage<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> pool<span class="token operator">:</span> ConnectionPool<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">BatchHandlingResult</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> success<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">;</span> newCheckpoint<span class="token operator">:</span> bigint <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> success<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token string">'IGNORED'</span> <span class="token operator">|</span> <span class="token string">'FURTHER'</span> <span class="token operator">|</span> <span class="token string">'OLDER'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">storeProcessorCheckpoint</span><span class="token punctuation">(</span> execute<span class="token operator">:</span> SQLExecutor<span class="token punctuation">,</span> options<span class="token operator">:</span> <span class="token punctuation">{</span> processorId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> newCheckpoint<span class="token operator">:</span> bigint <span class="token operator">|</span> <span class="token keyword">null</span> lastProcessedCheckpoint<span class="token operator">:</span> bigint <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">;</span> partition<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>BatchHandlingResult<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> result <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">single</span><span class="token punctuation">(</span> execute<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">command</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token punctuation">{</span> result<span class="token operator">:</span> <span class="token number">0</span> <span class="token operator">|</span> <span class="token number">1</span> <span class="token operator">|</span> <span class="token number">2</span> <span class="token operator">|</span> <span class="token number">3</span><span class="token punctuation">}</span><span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">SELECT store_processor_checkpoint( </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>options<span class="token punctuation">.</span>processorId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>options<span class="token punctuation">.</span>newCheckpoint<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>options<span class="token punctuation">.</span>lastProcessedCheckpoint<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, pg_current_xact_id() ) as result;</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> result <span class="token operator">===</span> <span class="token number">1</span> <span class="token operator">?</span> <span class="token punctuation">{</span> success<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> newCheckpoint<span class="token operator">:</span> options<span class="token punctuation">.</span>newCheckpoint<span class="token operator">!</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span> success<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> result <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token string">'IGNORED'</span> <span class="token operator">:</span> result <span class="token operator">===</span> <span class="token number">2</span> <span class="token operator">?</span> <span class="token string">'FURTHER'</span><span class="token operator">:</span> <span class="token string">'OLDER'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>As you can see, thanks to:</p> <ul> <li>global ordering,</li> <li>checkpoint detection,</li> <li>storing checkpoint where our side effects will be stored,</li> <li>transactional capabilities of our end storage,</li> </ul> <p>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.</p> <p>We’re getting by that generic idempotence check and detection of the noisy neighbour.</p> <p>Of course, I still believe that <a href="/en/idempotent_command_handling/">idempotence check should happen on the business logic side</a>. But why not both?</p> <p>Being able to detect a noisy neighbour can help you automatically stop (or pause) one of the competing consumers and avoid inconsistency conflicts.</p> <p><strong>What are the tradeoffs of this approach?</strong></p> <ol> <li>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 <a href="https://github.com/event-driven-io/emmett">Emmett</a>, Marten or KurrentDB, Kafka, this will work, but not necessarily for solutions like RabbitMQ, SQS, Google Pub Sub, etc.</li> <li>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.</li> <li>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.</li> </ol> <p>As always, the choice is yours.</p> <p>Still, I hope that this article will show you why:</p> <ul> <li>having a global ordering guarantee can be useful,</li> <li>why and how to checkpoint your processing, how they relate to level codes from old games like Super Frog,</li> <li>What are the tradeoffs, and how to consider them,</li> <li>…and that SQL IF statements are sometimes justified. But don’t go wild with them!</li> </ul> <p><strong>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.</strong></p> <p>What are your thoughts? Questions? Concerns?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Consumers, projectors, reactors and all that messaging jazz in Emmett]]>https://event-driven.io/en/consumers_processors_in_emmett/https://event-driven.io/en/consumers_processors_in_emmett/<p><strong>Did you know that you can build an event store in one hour?</strong> 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:</p> <p><a href="https://www.youtube.com/watch?v=gaoZdtQSOTo"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 57.00000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAC4jAAAuIwF4pT92AAADIElEQVQoz1WR7UtbdxzFvzeJDxWrae1aq5kmMd6YB23siImGWNa0bDBhFqvCGKMVCmNMuuIG215Mt5hmmt4YNc/P8V6NqZJo+6YvAv1fCns1erkY2GC1vzOkG2OvDue8+HA4h8zZ0Cs+GVbM0c3XRmFTdkVzsmdblG3+jHzNX5KtP+3Jlh8rsnlpXzYsVmWj965MRLJK0yo3nWuTSdN85n8nor+IqEb2nfCf5vQT8LEwTJubuJEp4sOECKdQgCu0C+MPWRi+zaNvsQjLozIu9fEgIlB7D6Zm5rG0cAcqFfcuI3pJ1p3QyVBBQP/2r2/1G+tMty6w3kCEmdbSzBwsssHlAtN/l2Hdi3nGP4iwTq2WERGji1Zmd3/MhIf3WOf5tjdnQI6jOg2Vgo2x3S24xRhzpLbgSiXhjGcwkZLgie7B9WQPY8Eyrv1yCOvCKlo1BOJaQZfscDh9mPJ4zpqd/tOwTo79UOOLF2XM1krss2f7WHhWxVzlKT5/eoR5qYbZfA1ziUPMpF5g4is/iDQYMUyA77uObss4jP2G/wNtlbWGqRhAf9LPLDkBMwcSZitl3K8e4654CGuogIH7jzD4zQaGH6yASI3RK4OYtroxcNX073b/Ac17/oZdDMJZjrAPxC04C9twZuPw5PJwxXMYETKw/RzFkL+E4S9X0NxyERe6zLg3/il8Fg+I04BTN5+eKXHqOlkPAg1e8mP0YIN9cpTD9GERPimLqbKEaakMX1qEdT2DgUAJtodBdHa9Dzp3BUadDZe79KBmLVRN509J0w5St9XJVn18MloN4cZx/O2tWord3E8wr5hgt3fzzJfPM28yx7zxHcYHMmxkOcG6uvWMqIXZdBZmu/reu8eJ3nAqNdQqdZ0cz9f+uP5cgPMogslaDJOVGCbLSXilNLyFDMYTadjDCZhWY7CFJBhct9Ch7kDTBR43XQ7MTTqgbe/ER2Ne9Pb0v6SJyupvY6WVE0f0e8WcXVaGpXXFIW4ow/mIYomHFT4cVixCVOEfbytDoaIysrCktHIqhbgWpVdnUL6en1L0PbrX7lH3aYf28vHf7cOiJvvA28MAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="talk" title="talk" src="/static/413e859a89c68825dcfae3efa4a7be9e/a331c/talk.png" srcset="/static/413e859a89c68825dcfae3efa4a7be9e/36ca5/talk.png 200w, /static/413e859a89c68825dcfae3efa4a7be9e/a3397/talk.png 400w, /static/413e859a89c68825dcfae3efa4a7be9e/a331c/talk.png 800w, /static/413e859a89c68825dcfae3efa4a7be9e/8537d/talk.png 1200w, /static/413e859a89c68825dcfae3efa4a7be9e/cbfa2/talk.png 1337w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p><strong>Yet, my final thought was: Kids don’t do it at home.</strong></p> <p>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?</p> <p>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.</p> <p>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:</p> <ul> <li>appending an event at the end of the stream,</li> <li>reading all events from the stream,</li> <li>a guarantee of the ordering within the stream,</li> <li>being able to read your writes,</li> <li>strong-consistent, atomic writes and optimistic concurrency.</li> </ul> <p>That can be solved with knowledge about transactions, database design, etc. So again, why so hard?</p> <p>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.</p> <p>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?</p> <p>I’ve been working on the message processing architecture in <a href="https://github.com/event-driven-io/emmett">Emmett</a> 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.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/14483a35803bc4c472795101685aa51a/b8a4c/Consumers-Processors.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 85.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAARABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB9yzUcxZqiwH/xAAWEAEBAQAAAAAAAAAAAAAAAAABEAD/2gAIAQEAAQUCg4YYv//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQMBAT8BH//EABQRAQAAAAAAAAAAAAAAAAAAACD/2gAIAQIBAT8BH//EABYQAAMAAAAAAAAAAAAAAAAAAAEQIP/aAAgBAQAGPwJG/wD/xAAaEAADAAMBAAAAAAAAAAAAAAAAATERIEFx/9oACAEBAAE/IUmMyV0Q1B+DW3R//9oADAMBAAIAAwAAABDIBzz/xAAWEQEBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQMBAT8QRc//xAAVEQEBAAAAAAAAAAAAAAAAAAAQAf/aAAgBAgEBPxAh/8QAHRABAQACAQUAAAAAAAAAAAAAAREAITEQQVFxof/aAAgBAQABPxBlqc0yw128BiKgxhDEDWmbMOtV8yzzRGyZwffRz//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Consumers &amp; Processors" title="Consumers &amp; Processors" src="/static/14483a35803bc4c472795101685aa51a/c60e9/Consumers-Processors.jpg" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="why-split-consumers-and-processors" style="position:relative;"><a href="#why-split-consumers-and-processors" aria-label="why split consumers and processors permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why Split Consumers and Processors?</h2> <p>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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p>These concerns are orthogonal.</p> <p>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.</p> <p><strong>Consumers</strong> 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:</p> <ul> <li>A PostgreSQL event store, polling the events table,</li> <li>EventStoreDB, using push-based catch-up subscriptions,</li> <li>Kafka, consuming from topics,</li> <li>Any other message source you might have.</li> </ul> <p><strong>Processors</strong> are responsible for doing something meaningful with those messages. They handle the “what do we do with messages” concern. A processor might:</p> <ul> <li>Update a read model in PostgreSQL or MongoDB,</li> <li>Call an external API when certain events occur,</li> <li>Publish events to Kafka or fire webhooks,</li> <li>Trigger workflow steps or saga operations.</li> </ul> <p>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:</p> <ul> <li>You can run the same projection logic against different event sources</li> <li>Adding new processors doesn’t require changing consumer code</li> <li>You can test processors in isolation with fake event streams</li> <li>Consumers and processors can be scaled independently</li> </ul> <p>Let me show you how this looks in practice.</p> <h2 id="how-consumers-work" style="position:relative;"><a href="#how-consumers-work" aria-label="how consumers work permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How Consumers Work</h2> <p>I deliberately decided to keep Consumers as dumb as possible. A consumer’s entire job is:</p> <ol> <li>Connect to a message source,</li> <li>Poll messages in batches or subscribe to notifications (depending on the source specifics).</li> <li>Forward them to all registered processors.</li> <li>Go back to step 2.</li> </ol> <p>That’s it. No business logic. No complex state management. No decision-making about what to do with messages. Consumers are essentially routers.</p> <p>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.</p> <p>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).</p> <p>Here’s what a basic consumer setup looks like for PostgreSQL :</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> consumer <span class="token operator">=</span> <span class="token function">postgreSQLEventStoreConsumer</span><span class="token punctuation">(</span><span class="token punctuation">{</span> connectionString<span class="token punctuation">,</span> processors<span class="token operator">:</span> <span class="token punctuation">[</span> shoppingCartDetailsProjector<span class="token punctuation">,</span> customerAnalyticsProjector<span class="token punctuation">,</span> orderNotificationReactor <span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> consumer<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> consumer <span class="token operator">=</span> <span class="token function">eventStoreDBEventStoreConsumer</span><span class="token punctuation">(</span><span class="token punctuation">{</span> connectionString<span class="token punctuation">,</span> from<span class="token operator">:</span> <span class="token punctuation">{</span> stream<span class="token operator">:</span> <span class="token string">'$ce-roomRservations'</span><span class="token punctuation">,</span> options<span class="token operator">:</span> <span class="token punctuation">{</span> resolveLinkTos<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <h2 id="why-batching-belongs-to-consumers" style="position:relative;"><a href="#why-batching-belongs-to-consumers" aria-label="why batching belongs to consumers permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why Batching Belongs to Consumers</h2> <p>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 <a href="/en/batching_async_enumerable/">Why you should batch message processing in my other article</a>.</p> <p>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.</p> <p>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 <a href="/en/projecting_from_marten_to_elasticsearch/">Elastic prefers batching updates</a>.</p> <h2 id="how-processors-work" style="position:relative;"><a href="#how-processors-work" aria-label="how processors work permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How Processors Work</h2> <p>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:</p> <ul> <li><strong>Processing logic</strong>: Actually doing something useful with events. Updating a read model, sending an email, calling an API.</li> <li><strong>Checkpointing</strong>: Tracking which messages have been processed. This is crucial - without it, you’d reprocess everything from the beginning every time you restart.</li> <li><strong>Error handling</strong>: Deciding what to do when processing fails. Retry? Skip? Stop everything?</li> <li><strong>Idempotency</strong>: 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 <a href="/en/idempotent_command_handling/">my other article</a>,</li> <li><strong>Backpressure:</strong> 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.</li> </ul> <p>Those are general promises and common stuff for the message processing logic. Still, there are multiple reasons why you want to process incoming messages:</p> <ul> <li><strong>Read models (projections)</strong> transform events into queryable state. For instance, shopping cart events - <em>ProductItemAdded</em>, <em>ProductItemRemoved</em>, <em>ShoppingCartConfirmed</em> - need to become a document showing current items, quantities, and totals. Something your API can quickly return when a user opens their cart.</li> <li><strong>Reactions</strong> 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.</li> <li><strong>Workflows</strong> 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.</li> <li><strong>Integration</strong> 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.</li> </ul> <p>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 <em>quite</em> different from forwarding a message to Kafka.</p> <p>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 <em>archetypes</em>:</p> <ul> <li>projectors,</li> <li>reactors,</li> <li><a href="https://www.architecture-weekly.com/p/workflow-engine-design-proposal-tell">workflows</a>,</li> <li>allow custom message processors to allow people to tune it fully to their needs,</li> <li>and in the future, stuff like forwarders, web hooks and others we find useful.</li> </ul> <p>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.</p> <p>That’s also why each message processing target (PostgreSQL, EventStoreDB, MongoDB, InMemory, Kafka, SQS, etc.) will have its own implementations.</p> <p>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.</p> <p>Read also more in:</p> <ul> <li><a href="https://github.com/event-driven-io/emmett/pull/257/files">My RFC for Workflow Processing</a></li> <li><a href="https://www.architecture-weekly.com/p/compilation-isnt-just-for-programming">How message pipelines can be technically implemented</a>.</li> </ul> <p>The example projector can look like that:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> projection <span class="token operator">=</span> <span class="token function">pongoSingleStreamProjection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> collectionName<span class="token operator">:</span> shoppingCartsSummaryCollectionName<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function-variable function">initialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'pending'</span><span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> postgreSQLProjector <span class="token operator">=</span> <span class="token function">postgreSQLProjector</span><span class="token punctuation">(</span><span class="token punctuation">{</span> projection <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> reactor <span class="token operator">=</span> <span class="token function">postgreSQLReactor</span><span class="token punctuation">(</span><span class="token punctuation">{</span> processorId<span class="token operator">:</span> <span class="token string">'order-notifications'</span><span class="token punctuation">,</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function-variable function">eachMessage</span><span class="token operator">:</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=></span> emailService<span class="token punctuation">.</span><span class="token function">sendOrderConfirmation</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>customerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="native-implementations-of-processors" style="position:relative;"><a href="#native-implementations-of-processors" aria-label="native implementations of processors permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Native implementations of processors</h2> <p>Different storage requirements require different capabilities, and getting proper guarantees might involve deeper knowledge. For instance, <a href="/en/ordering_in_postgres_outbox/">Postgres sequences issues can impact your messaging guarantees</a>. 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 <a href="https://github.com/event-driven-io/emmett">Emmett</a> solve them? Let’s discuss them briefly. I’ll try to expand in the future posts about the details.</p> <h2 id="resilience" style="position:relative;"><a href="#resilience" aria-label="resilience permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Resilience</h2> <p>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.</p> <p>Why? Consider this scenario: you have two processors, one updating MongoDB and another updating PostgreSQL. MongoDB becomes temporarily unavailable. Should that stop PostgreSQL updates?</p> <p>Still, failure behaviour is configurable; your message handler can return:</p> <ul> <li><strong>void/ACK</strong>: message processed successfully, continue to the next one.</li> <li><strong>SKIP</strong>: Skip this message, useful for poison messages that consistently fail</li> <li><strong>STOP</strong>: Stop this processor entirely.</li> </ul> <p>Why have <em>skip</em> separate from <em>ACK</em>? Consider a poison message - a message that causes your processor to fail every time. Without <em>skip</em>, you have two bad options: fail forever (blocking all processing) or <em>ACK</em> it (pretending you processed it). With <em>Skip</em>, you can move it to a dead-letter queue for investigation while continuing to process other messages.</p> <p>For now, <a href="https://github.com/event-driven-io/emmett">Emmett</a> 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.</p> <p>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).</p> <p>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.</p> <p>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.</p> <h2 id="checkpointing-processing" style="position:relative;"><a href="#checkpointing-processing" aria-label="checkpointing processing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Checkpointing processing</h2> <p>In Emmett, <strong>processors own their checkpoints</strong>. Each processor independently tracks the last message it processed. The consumer doesn’t maintain any checkpoint state.</p> <p>When a consumer starts up, it asks all registered processors for their last processed position and starts polling from the earliest one.</p> <p>It has several benefits:</p> <ul> <li><strong>Independent progress</strong>: 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.</li> <li><strong>Isolated failures</strong>: If one processor’s checkpoint storage fails, only that processor is affected. Others continue working.</li> <li><strong>Easy replay</strong>: To rebuild a single projection, you just reset that processor’s checkpoint. No need to coordinate with other processors or manage a global position.</li> <li><strong>Flexibility</strong>: Processors can store checkpoints wherever makes sense - in the same database as their read model, in a separate checkpoint table, or anywhere else.</li> <li><strong>Capability to redistribute the load.</strong> 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.</li> </ul> <p>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.</p> <h2 id="backpressure-when-processors-cant-keep-up" style="position:relative;"><a href="#backpressure-when-processors-cant-keep-up" aria-label="backpressure when processors cant keep up permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Backpressure: When Processors Can’t Keep Up</h2> <p>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.</p> <p><a href="https://github.com/event-driven-io/emmett">Emmett</a> doesn’t support it at the moment, but here’s what I’m thinking about it.</p> <p>There are several strategies, each with tradeoffs:</p> <p><strong>1. Ignore backpressure</strong>: Consumer keeps polling and pushing regardless of processor state.</p> <ul> <li>Pro: Simple, maximum throughput when processors can keep up</li> <li>Con: Memory grows unbounded, possible OOM, cascading failures</li> </ul> <p><strong>2. Stop on any slowdown</strong>: If any processor signals it’s overwhelmed, stop polling.</p> <ul> <li>Pro: Safe, no resource exhaustion</li> <li>Con: Slowest processor determines overall throughput</li> </ul> <p><strong>3. Force synchronised pace</strong>: All processors must process each batch before the next is fetched.</p> <ul> <li>Pro: All processors stay in sync, predictable memory usage</li> <li>Con: The Slowest processor becomes the bottleneck for all</li> </ul> <p><strong>4. Slow down ingress</strong>: Adaptively reduce polling rate based on processor feedback.</p> <ul> <li>Pro: Balances throughput and stability</li> <li>Con: More complex, needs tuning</li> </ul> <p><strong>5. Rolling buffer</strong>: Buffer messages up to a limit, retry delivery to slow processors.</p> <ul> <li>Pro: Absorbs temporary slowdowns, maximises throughput</li> <li>Con: Needs memory limits, complex failure handling</li> </ul> <p>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).</p> <p>I’m leaning toward making this configurable per consumer, with sensible defaults. The default would be a bounded buffer with adaptive polling slowdown.</p> <h2 id="scaling-current-state-and-future-plans" style="position:relative;"><a href="#scaling-current-state-and-future-plans" aria-label="scaling current state and future plans permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Scaling: Current State and Future Plans</h2> <p>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.</p> <p>You can group processors into consumers by that, reducing the number of polling jobs (one consumer polls/subscribes to one source).</p> <p>I already mentioned batching, which should also increase the throughput.</p> <p>Running multiple instances of the same processor causes conflicts. Both process the same events, update the same read models, and corrupt the state. <a href="https://github.com/event-driven-io/emmett">Emmett</a> already has the basic capability to do <a href="https://www.architecture-weekly.com/p/distributed-locking-a-practical-guide">distributed locking</a>, but it’s not fully plugged yet. This will come in future releases.</p> <p>For now, checkpointing can detect whether a newer checkpoint is already stored (which can suggest another processor is running) and stop processing.</p> <p>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.</p> <h2 id="rebuilding-projections" style="position:relative;"><a href="#rebuilding-projections" aria-label="rebuilding projections permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Rebuilding Projections</h2> <p>Event Sourcing enables rebuilding read models from events. Bug in projection? Fix code, rebuild. New read model? Populate from history.</p> <p>With processor-owned checkpoints, you can either rebuild read model from scratch by:</p> <ol> <li>Stopping the processor.</li> <li>Delete read model data.</li> <li>Reset the checkpoint to the beginning.</li> <li>Restart processing.</li> </ol> <p>Or doing blue greeen by:</p> <ol> <li>Creating a new version of your storage (with Pongo, it’s just adding a suffix or prefix to your collection name).</li> <li>Start consumer since the beginning.</li> <li>Check if read models are close enough, and stopthe old processor</li> <li>Start processing.</li> </ol> <p>In <a href="https://github.com/event-driven-io/emmett">Emmett</a> you have even some syntactic sugar on top of consumers and processors to make this easier:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> rebuildPostgreSQLProjections <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> rebuilder <span class="token operator">=</span> <span class="token function">rebuildPostgreSQLProjections</span><span class="token punctuation">(</span><span class="token punctuation">{</span> connectionString<span class="token punctuation">,</span> projection<span class="token operator">:</span> shoppingCartsSummaryProjectionV2 <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> rebuilder<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>We’ll need more metrics like gap detection and distributed locking to make it more plug-and-play.</p> <h2 id="wrapping-up" style="position:relative;"><a href="#wrapping-up" aria-label="wrapping up permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Wrapping Up</h2> <p>The consumer/processor architecture in <a href="https://github.com/event-driven-io/emmett">Emmett</a> is about making event processing concerns explicit and separable:</p> <p><strong>Consumers</strong> handle delivery - getting events from sources to processors. They’re simple by design. When delivery breaks, you know where to look.</p> <p><strong>Processors</strong> handle processing - doing useful things with events. They own their checkpoints, track their own progress, and handle their own failures.</p> <p>This separation gives you:</p> <ul> <li>Flexibility to mix and match consumers and processors,</li> <li>Independent scaling of different processing workloads,</li> <li>Isolated failure domains,</li> <li>Easy projection rebuilds,</li> <li>Testability at multiple levels.</li> </ul> <p>The design makes tradeoffs explicit:</p> <ul> <li>Partial progress over all-or-nothing (configurable soon),</li> <li>Processor-owned checkpoints over global tracking,</li> <li>Simplicity in consumers, complexity in processors,</li> <li>Eventual consistency for async operations.</li> </ul> <p>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.</p> <p>I hope that this is a good food for thought, even if you’re not using <a href="https://github.com/event-driven-io/emmett">Emmett</a>. I’m curious about your thoughts and feedback. I’ll try to tackle those cases in more detail in dedicated articles.</p> <p>If you have questions, feedback, or would like to help me speed up the planned stuff, come chat in the <a href="https://discord.gg/fTpqUTMmVa">Emmett Discord</a>. We have a small, but welcoming and awesome community.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Requeuing Roulette in Event-Driven Architecture and Messaging]]>https://event-driven.io/en/requeuing_roulette_in_messaging/https://event-driven.io/en/requeuing_roulette_in_messaging/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto" > <a class="gatsby-resp-image-link" href="/static/7523e8bd658f1926a8c638e369ddc6fe/b09c1/2025-11-17-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAAChElEQVQ4y4WTzU8TURTF5z9y4d6lG5duXBgXmrgxxhB2JgZXGhIXbgxETCTRUI0JJARRUTAgFqUUSpG2VEqb0o9ppzPTdmba+f6ZN9CKbDzJzb2Z9+a8c899TwIIwzCKQT2A53n0+3263S6yLEfRaDRQFIVer4fjOMO9Aw7pLMGgDoIAy7LQdR1VVTEMI/o2WBNkrVZreIhpmsP/pfPKhJq9vT2WlpZIp9OUy2UODw8pFArE4/Eon4XruhGpUC0gnSVrKQpKs4lt2+RyOebm5ojFYlFeXFwkm81Gis/bJCCs6HTafxXa/R5ys/HP6aIVVW1FrQvl560Z1mGI5wdU68pfwrqi0qnKEIKsqKytb5BIJNneSbG1tc369zibmwmSyWS0XwxLeOvabXyvTWDX0YsrSL7vQ2BTyiTQfu/it8oYzRKVg21qx3nqtSNq1SKV40KUm6ddaJoWeRsEHaBDYGbp/ppE8oOQ0Cmivb6DuT6B/v4BTvwpemwUaquAMFsM4ug0O6LD4dTVWhHD1DCNNq7VREqlUlhGm6OFWcrLS5QSa8j7CTLz8ygbH3GOV2k3q+gtGV2p49j2yfURnQE/F58zN3OLxtEK9mEVSSzUKgG7mxqZ3S6lEuT3PfIFKC5/ojJ9l4OUQS7tkdvtYxre6X08ISysvWVv5gZm4wd+z0X6OjXB2rspZh+OMH3vOssvp+jqHrrqotdk9FIGXe3T1lzaqoPrnBDpmkaxXMGrp+l+GMfX8ri+jjR18QKvrlzi29hNvjwZY/7+NXzf5n+wLBOt06UvZ5CXn+HqeSxLRnpz9TKfR0bZmRxn/tFtFl48Hj6xcBCnl/h8CPi+hxiPH3iEoc8f45XB6cQmeFYAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/7523e8bd658f1926a8c638e369ddc6fe/b09c1/2025-11-17-cover.png" srcset="/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" sizes="(max-width: 626px) 100vw, 626px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I’m always saying that there’s a thin line between good and bad practice, and this thin line is named “Context”.</p> <p><strong>That’s also true for the (anti-)pattern I’m calling <em>“Requeuing Roulette”</em></strong>. Let’s discuss it today, continuing the “race condition series”:</p> <ul> <li><a href="/en/dealing_with_race_conditions_in_eda_using_read_models/">Dealing with Race Conditions in Event-Driven Architecture with Read Models</a>,</li> <li><a href="/en/strict_ordering_in_event_handling/">Handling Events Coming in an Unknown Order</a>.</li> </ul> <p>What’s the Requeuing Roulette? As the name suggests, this <em>technique</em> 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.</p> <p>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).</p> <p>If the consumer is not available, the messaging system will try to deliver messages and handle retries for us.</p> <p>We discussed it in detail in:</p> <ul> <li><a href="https://www.architecture-weekly.com/p/architecture-weekly-190-queuing-backpressure">Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency</a></li> <li><a href="https://www.architecture-weekly.com/p/the-order-of-things-why-you-cant">Ordering, Grouping and Consistency in Messaging systems</a></li> <li><a href="https://www.architecture-weekly.com/p/the-order-of-things-why-you-cant">The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems</a></li> <li><a href="https://www.architecture-weekly.com/p/dealing-with-eventual-consistency">Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers</a></li> </ul> <p>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.</p> <p>What does the smartass “causally correlated” even mean? For instance:</p> <ul> <li>depositing money into a bank account is causally correlated to opening it, as we can’t deposit money if we don’t open it.</li> <li>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),</li> <li>money withdrawal is causally correlated to depositing money and other withdrawals, as we need to check the balance, and they may impact it.</li> <li>Withdrawals and deposits are only causally correlated if they happen on the same bank account; other bank account operations can happen at any time.</li> </ul> <p>You get the idea, aye?</p> <p>So if we set up a queue to process money transfer events, then it could look as follows:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5cfc485c64729c5d89659db7932c40b1/1ce36/queue1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABfklEQVQoz42RTUsbURSG8wtdFME/0GVX4tati1IQ2k0RW6KlVLpyGdFijSYTFIxgqrF2Go2ZzqQzma/LJGNm5j4y14ijUO2Bw7m8572H595TohBSSv4VT/WKUbozyyxTQup2GZunpCOBaVrYtk026f3P0NJjU1hZ4Or9DMHlMZ2ugWH0HlA+l4rQcRyazSaeH9DdWkb/Os9Fax/9d4eTHy3+9vv3hDKnzeskKWh3hHEc47ouw9GIRMI4AxENcXxB3x4QxQn5lbyX11RCyiQLmiLM/ycndFyPNPIJqkuEjU+Mz7fxthaRnRqiXibUVrk+32FQec34V5Ww9lH54rNvDDbeMNQ1NVgNrNdrfK/ukQQWFx9eYqzNEWll9HfTRPufuVx9hbk2i9BW+Lk4xfDgi9Kucl+jjP72BdHRunp0KUkSfM8jCAUj4dM/3mHQriN6J5jNTYRxhtXaxWlrhL1T/hxWEL220ux2Q/mso00iS78lLG45K5zlo/qclk2WdgNPD6Ri3EH1BAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="queue1" title="queue1" src="/static/5cfc485c64729c5d89659db7932c40b1/a331c/queue1.png" srcset="/static/5cfc485c64729c5d89659db7932c40b1/36ca5/queue1.png 200w, /static/5cfc485c64729c5d89659db7932c40b1/a3397/queue1.png 400w, /static/5cfc485c64729c5d89659db7932c40b1/a331c/queue1.png 800w, /static/5cfc485c64729c5d89659db7932c40b1/1ce36/queue1.png 966w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I assumed that we’re following the advice from the <a href="/en/strict_ordering_in_event_handling/">previous article</a>. 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.</p> <p>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:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6af9167e8b2edcb44058795d401b5833/1ce36/queue2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB0ElEQVQoz22Sy27TUBCG85aV2PQJYIeKkHgAti3dcRVIJZukaUWCoLRCagJdkBKwY8d27DiXhkS52Wkc28f2h5xEQiL9pU8jjc6ZmfPPySRJwhZxTCrbblG+vOTq6jvVapVKpUJdlrnzzoYM/ynZRN8POb/4Si5foFT6SKFwwqfPZxyfnDIaT9dnE7aUSauGYbgiCHyENyPyZgjfxdYlNKWGoSuoqoTRkNCkHyznY4RYEoYewp8TeRPE0iWOYzJRFBEJsaoeeGOcL08ZFZ4g6lmc/B4oWaAL9KB3hpN/hH/9hlgYxMJEWCWGRw9YXh8RJpsJXcdhMBxB5NDN7WG/u8+i+gLt8B6T8jOgA9zg6znq+zvMyvubJn18I49+uIP77TnpWCsPU8OLxSLOdEZH0WjLKn9Mm9Yvib5uMlfPceUiI0vH+q0waJq4jQtc+QMj08CqyQxbNqEQZNJ3u66L590ynSwxVDB1MNRkFc1GzPD9Q4avd2n/7GA0oaUG9LOPGbzapV3rouvQaSUEQbj2MF1IqtnUo6kusLQ15ireIp2+RM4fYEk9TD2grbvUi29Rjg+w6zc0NZ9ua0EQBGsPV4vZIMQdxKwR8b9ctJ1L/+FfN+qWaoO0PJMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="queue2" title="queue2" src="/static/6af9167e8b2edcb44058795d401b5833/a331c/queue2.png" srcset="/static/6af9167e8b2edcb44058795d401b5833/36ca5/queue2.png 200w, /static/6af9167e8b2edcb44058795d401b5833/a3397/queue2.png 400w, /static/6af9167e8b2edcb44058795d401b5833/a331c/queue2.png 800w, /static/6af9167e8b2edcb44058795d401b5833/1ce36/queue2.png 966w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>It’s fine</strong> to process events that are <strong>not</strong> causally correlated, as by that we’re increasing throughput, not trading off correctness:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/618952684101989d1157806f1f8a1805/499a5/queue3.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABtUlEQVQoz52SX0/aUBjG+bjLLvYpduFncDdL5lxmWIwxjDkFNWwwSGzMxAgotIgUqKXUQqE9p6e/pQdIdru9yZOTPO+T5/13cipNycIye5RLJS4vLqhWq5TLJX7+qFCrVvE8T2vSjXb7QkqiJCpVSBmhVEIuSRKdqvy6In9U4PD4G3t7+xSK38kffeXjpwNub1tak0m3Xv3+I0P7kWXzM0H9HanoIMWMXJaMJi3cq0Oc6wLu7yIzr8f8+Q6nkccbGIThkERMkLHDtgHffyGWS+bX+wS1XUjMteE0CHlpHuMcvGF6tsP05C0wIfUbDD68JrVPs4WAuCWV90gptKFSCtd1sUc24/GIWWgjE0HupnmDM/Sx7kZYbYdBx8Gv7PJsnGF2Zgx7U+x+oDEeBAgh2YZhGNQbDepGg0nU0Zwe2X4SdNsK817R60j6X3Ywz4v0TOi2YrotoWE9CKRI9DFEHKMURIFE+HN8N2G1SshJKRFCIGKhq2dYRYIoEmt+w22RjaqPYnVpPzhY56c8vX9Fy/CYunLd4b9G9m2Wy5BFKBm2u1i1E+z+gnCRkMuS/4ON899lNP4AcomiWD0RIA8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="queue3" title="queue3" src="/static/618952684101989d1157806f1f8a1805/a331c/queue3.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>But <strong>it’s not fine</strong> 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.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/aa7870589930c2228d57677cd0d03c59/a88fd/queue4.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABmElEQVQoz22SW2tTQRSFz//zb/h7ii+CIIgKgg3BQgspRftQ6UNJSTgxMZpLW2I8t5iTTNLc5rLPJ+cco3lwYM1m1qy92WtmewBZluWB4WDAbb1Oq+Xj+z7NZoNOp00cR+x1e22+nLU4ZxERnEgRveLgXCFofvlK5eSU40qVWu2c98cVqtUPRFFS3IuUxZRSjEYjxOR8ymP9DcubVwgOL5eI06w7Zyj/hOj6LfOkzePygcWsh5r1cTpATIA1qii43W5J0xSn58AG1btk3j1HMoMXxQmzacTk8hnp1XOmn45g0wG5B1dC9BDZ9bB6gshfxzSu3tHpnrFOIjKbkTfnjcc/GI9D7geOfndDfwBh7QXhxWuGfbj7tuXu+45hd8evROd+8scsCn58+ZTryhM2QQ+7XBecl2/WZiShZpIjgZ/+DVG7QRKXfBJp4kCzUKZIWq9WTGcK8/CZXbuKiMLZKdasy0+Rwkf2BweeOOT3gMVCEcQT5q1TwosjkAliQqxZlR2WI3EAkRIZ/8G/0THWYKzGOYOzGhHHb62LWhuY97TVAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="queue4" title="queue4" src="/static/aa7870589930c2228d57677cd0d03c59/a331c/queue4.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>We actually have two orders in messaging systems:</strong></p> <ol> <li><strong>Queue Order:</strong> The order messages are produced to the queue</li> <li><strong>Processing Order:</strong> The order messages are actually consumed and processed.</li> </ol> <p>We already learned that we can detect the out-of-order processing by:</p> <ul> <li><a href="/en/dealing_with_race_conditions_in_eda_using_read_models/">business rules or</a>,</li> <li><a href="/en/strict_ordering_in_event_handling/">detecting the gap between the last processed revision and the event revision</a>.</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e58596f6c0825885c45299432e65a67b/74506/queue5.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 30.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABVElEQVQY01WR3YrTUBSF88q+iSBee6EgA15UC1atUIfRQUUqjhrHaWycZuwkTW3ac5KcnN9PEpDR72qz91rsvyh4T49UlmfTGaPHYyaT54xGY+L4+1ALgLWWqqqwRmDbAp1/pSpr8rVHXuV43Q3aqBd7LZEXx8Svjjg/OyH9+Zn4yxuqXQphh/caYx1FkQMKf0goj+8wf/2L6bhhs6z5S7TZ7sjTmO3Lu+xOH0BxCjYFl+PUik4mGH1jeDe7x/LbC7xo+lGG3Hxzn8X1W6rfgkiIA0UuWCaeJIXL6UMuT2YsLuDHecsi7pDCDOZ+m4+PbpG9v41tHGJvcAZW1QfW2wRxqIn6Dt4FVGPplKfeV7SyplOBTjlU6+jPrHVHts6pz57QJROyrOTp0Z5P8xKP4Wp1TZatiEII3PBv/D/Bh+Ex6rBB1yVGK2rhka3EhAbvA845/gC2ZsIKvfE2lQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="queue5" title="queue5" src="/static/e58596f6c0825885c45299432e65a67b/a331c/queue5.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We can also apply techniques like the <a href="/en/dealing_with_race_conditions_in_eda_using_read_models/"><em>Phantom record</em></a>, 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…</p> <p>But I promised you to talk today about the <em>Requeuing Roulette</em> (anti)pattern, aye? So let’s do it!</p> <p>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.</p> <p><a href="https://www.rabbitmq.com/docs/semantics#ordering">RabbitMQ message ordering documentation states</a>:</p> <blockquote> <p>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.</p> <p>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.</p> <p>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.</p> </blockquote> <p>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.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d4eaac01d4cccdb434695c1d0d8cd093/a60fa/queue6.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 36.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAHCAYAAAAIy204AAAACXBIWXMAAAsTAAALEwEAmpwYAAABcUlEQVQoz1WR22rbQBCG9ap9lb5BL/MAhV4WetMDuIXQUJPGjp0WGecg2apknSzrsCutDquvSHITOjDwD7v/t7MzBoDWGtM0WS6XrFYr1uvVWAshiOOYPM+RUlIUxViXZcnk6+n7nn8xaGMSGqUqbMvi62yGbVuEYTAC4cUwRNd1RHGCqtsXUNeOsOGmMYr/PciqJsnEGTAdCiFJjiGQ0RyvOdmXLOaKX9cS5QTPXqPpoIqfSL694fT9AtodqfmB0+07wEM3Fn3foOqGNAkoipAq2VD4d2x/12w3CVFuUddq7N7wDgecx0fcn3OcxYJ8/Z5oOcMz74kOMZEXUis1vp6Fez69fUV4fwnt1Llkx9X+Atf7Q1lWw5c1QmhcF/wA3B+f8TY2ng/ursFzWtq2p1I1ke8QfnlNFd3wtC24nQuytCIXJ4IgQCmFMW1KD9M65/P4hz2eE6QsydIj2d1H6vSBh03GzZXETw50umG/d0bgXzYnEYHyRuhrAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="queue6" title="queue6" src="/static/d4eaac01d4cccdb434695c1d0d8cd093/a331c/queue6.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Unfortunately, it’s only a best effort. <a href="https://www.rabbitmq.com/docs/nack">Another place in the documentation states</a>:</p> <blockquote> <p>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.</p> </blockquote> <p>So in the worst case, it can even end up like that:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f5d4e42b55fdfe2071df4c27151d7766/a60fa/queue7.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 42%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABiElEQVQoz02S627TQBCF87S8DlL/IPUnQuIZ3MhCpZAmUArBbaAoTuvGjt1qHd/t9WX3Q05SpyONdnT2nKOZ0Yy01vTx8jZNQ5Zl5HlOXddIKSmKYoeFYUgcxyiljhqtee0x0q+AqqpwXRfDMBiPx5imyWQywfO8QdBzhBBopdj7Hc36avQCtE2zI/aRJgmLxWKXQRAcBQdulhckWc4+NKqph//BMExS8mdBnpf8tG6Yz+dY1g1XVz+wbZu2bXl68kGnUPxB3BlMPxdYs5TWF4P5qJY1dScRF6eE5gnx5Slb84SuWaO7B3S7RHUFbavI0pgo8igim9y3WP6tcFY5QXpHJXtOxygMBY+uhzu95v78K87vBavxJ6LZR4Rzj7/eUhblvgGlMd6/YXX9AepDU5R8C96xdG6Jo7QfWSGrFt+Hfl279BXe7IyNLfDWGll1NI0k3MaI87eUDwbuY8b3i5TnQNKpGqWGHTLMD+qQAzjU/QlFSUzy7wuNuGXjRvyaFjgbn25oV/Mfx7pdmMZiYO4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="queue5" title="queue5" src="/static/f5d4e42b55fdfe2071df4c27151d7766/a331c/queue7.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>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?</p> <p>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.</p> <h2 id="when-requeuing-roulette-is-helpful" style="position:relative;"><a href="#when-requeuing-roulette-is-helpful" aria-label="when requeuing roulette is helpful permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>When Requeuing Roulette is helpful?</h2> <p>It can be useful if:</p> <ul> <li>We want to get the best parallelism for message processing, and ordering best effort is good enough for us.</li> <li>Our messages are most of the time not causally correlated or…</li> <li>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,</li> <li>Our consumers are stable, not failing too often.</li> </ul> <p>As you see, those assumptions can be fragile and classical <em>famous last words</em>.</p> <p>Of course, we can use one of the techniques like:</p> <ul> <li>RabbitMQ routing key, correlation id,</li> <li>AWS SQS message group id, visibility timeout,</li> <li>Azure Service Bus sessions,</li> <li>etc. see <a href="https://www.architecture-weekly.com/p/the-order-of-things-why-you-cant">Ordering, Grouping and Consistency in Messaging systems</a> for details.</li> </ul> <p>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.</p> <h2 id="dangers-of-requeuing-roulette" style="position:relative;"><a href="#dangers-of-requeuing-roulette" aria-label="dangers of requeuing roulette permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Dangers of Requeuing Roulette</h2> <p>Even when order doesn’t matter, requeueing has a hidden cost that becomes visible under load.</p> <p>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.</p> <p>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.</p> <p>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).</p> <h2 id="what-about-kafka" style="position:relative;"><a href="#what-about-kafka" aria-label="what about kafka permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What about Kafka?</h2> <p>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.</p> <p>So Kafka, for the win? Hold your horses!</p> <p>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.</p> <p>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 <a href="https://event-driven.io/en/gdpr_in_event_driven_architecture/#log-compaction">retention policy kicks in and drops old messages from the partition</a>.</p> <p>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.</p> <p>Read more in <a href="https://www.architecture-weekly.com/p/kafka-consumers-under-the-hood-of">Kafka Consumers: Under the Hood of Message Processing</a></p> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>The “requeueing roulette” is a symptom of trying to solve a distributed systems problem with a technical solution.</p> <p>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.</p> <p>Still, cheating can take us far enough, but there’s always a danger that we’ll be caught and handcuffed.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p>Read also more in:</p> <ul> <li><a href="/en/dealing_with_race_conditions_in_eda_using_read_models/">Dealing with Race Conditions in Event-Driven Architecture with Read Models</a></li> <li><a href="https://www.architecture-weekly.com/p/the-order-of-things-why-you-cant">The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems</a>,</li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a>,</li> <li><a href="/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/">Dealing with Eventual Consistency and Idempotency in MongoDB projections</a></li> <li><a href="https://www.architecture-weekly.com/p/architecture-weekly-190-queuing-backpressure">Queuing, Backpressure, Single Writer and other useful patterns for managing concurrency</a></li> <li><a href="https://www.architecture-weekly.com/p/the-order-of-things-why-you-cant">Ordering, Grouping and Consistency in Messaging systems</a></li> <li><a href="https://www.architecture-weekly.com/p/the-order-of-things-why-you-cant">The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems</a></li> <li><a href="https://www.architecture-weekly.com/p/dealing-with-eventual-consistency">Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Handling Events Coming in an Unknown Order]]>https://event-driven.io/en/strict_ordering_in_event_handling/https://event-driven.io/en/strict_ordering_in_event_handling/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto" > <a class="gatsby-resp-image-link" href="/static/64d3faf9c17c4be8610248e5e577e4bc/b09c1/2025-11-03-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADrElEQVQ4ywXB7VPTBQDA8d8BioHdlTBF1BGDjT3g2BgPWw4EYXNDGhsyeRg4xOkUEDgfgAKhnMDsRORhwHEoT/HYoWdIUEZwiJqidZdmd931ohe96K/49vkIwbujCI0QEyqSELxXxq44PWFpTsKOVvFBejHhmjyyLPlUlpdwt8KGTpPKZwlSLBk2gjQF7JGkECrWEbpPTnD4XoSQj2LYGaVEpD5GqrMWidVDbIUf8dV5Yhvu0Xeri+Frp6l32DBX1NJ1zsXzc8fpsBo5Yi2iyGQiPLWCEMlRdkTEI+wQJfCx2kLHxGMahxcpvR7gTPc3uP2DBPr6GbhQQmdhFv16Bbm5Nu50+fivxcqy7zT3v/DQadUiNZYgKAvZKdYjCOJMMstqWNrYpqrRT5qrDs/tPjqrC3B568lrDfBLXzs/FiZTnKzivPMEP1zMo81pYq7GTsCiJijGQEi8hZAoLcKu/G4aAguMza8yPr/CjbGHfB0YwJ6VgtFqw1JYyrXGNq5mpWHQaelpOc/VU2YuGxVUGRTcsjsw51cjKEoIESUiRFpamdn6g+d//s3tuTWq/A+57F/Ed7OHbH0yemUsBe5qrCfdlJgymSvOos4go82cxEC+jpPGXJpO1xEWbyY4UoWQ4b3Lo2cvWXn5lpbBDVq6n9DWu0bryDqec2fovuLBm63loESKO1tPc4qCyZwE3roPM5Mnw2NI5WZ5JeFSE8Giwwjur0Z5sv2G6Z9e4J/YZHR6i6H7G0zPL/F5bTkXiu3UZWrQi/ejiRaxNyoS+X4RjRoxPk0sHUVl1BR5CJLmsSNCjjD5YJOV1+9YfPqKvsVVnv7+F89+fc/Kg0nqquwY0zQY5XFY4w9xQSsjJymB6H2RFCjFNGtUjDscSE31hMkthEYlIjR0jNM+skBj/wTNPfeYXd/m3T//Mj7wJfnpSkw6JZUaGWPZSWQnq8hRS1GKo/Hq4qjWJDJ0XI+9tIGIVCcfHkxCGJn8meXVdda2Nlh9vMDswjSPvp3jiu0Il6wG0pNVuFVxTJlTqC+24Pg0CdmhaM6mxuJNUtCWLKeu7AwHMk+xO0aNsDQ4ylT7JWaGbtDla2Lm+ln6e+/Q5bvOd0sPCFx0MpgtZ/laJS+m7jJQW4pLKaE9/RPK5LEMaxXcqnASbS5gj0yN0Hq2nJKTDvLMOZw6psbrOMrq+hqbr17z5tUaa71NfF+m5/3TJX7bfsHWaAc9J/TMZsTjij/AtE7NorcQe81F0gpd/A8PISe1nlezAwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2025 11 03 cover" title="2025 11 03 cover" src="/static/64d3faf9c17c4be8610248e5e577e4bc/b09c1/2025-11-03-cover.png" srcset="/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" sizes="(max-width: 626px) 100vw, 626px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>After the last article on <a href="/en/dealing_with_race_conditions_in_eda_using_read_models/">Dealing with Race Conditions in Event-Driven Architecture with Read Models</a>, I got such a <a href="https://www.architecture-weekly.com/p/dealing-with-race-conditions-in-event/comment/171420356">question from Ben</a>:</p> <blockquote> <p>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?</p> </blockquote> <p>That’s a good question, and good questions usually require more depth to give a precise answer. That’s what we’re here for!</p> <p>Let’s follow up and discuss how to determine whether we have complete information for our events!</p> <h2 id="what-we-learned-so-far" style="position:relative;"><a href="#what-we-learned-so-far" aria-label="what we learned so far permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What we learned so far</h2> <p><strong>Communication in messaging systems works like a department store. It has multiple cash registers and separate queues for each of them.</strong> 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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p><strong>In the last article, we discussed the payment verification workflow.</strong> 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.</p> <p>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.</p> <p>If we model that as the workflow, then it’d look like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">decide</span><span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> event<span class="token operator">:</span> PaymentVerificationEvent <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token operator">|</span> PaymentVerification <span class="token operator">|</span> <span class="token punctuation">{</span> document<span class="token operator">:</span> PaymentVerification<span class="token punctuation">;</span> events<span class="token operator">:</span> VerificationEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> current <span class="token operator">=</span> current <span class="token operator">??</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> event<span class="token punctuation">.</span>paymentId<span class="token punctuation">,</span> initialState<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// (...) other event handlers</span> <span class="token keyword">case</span> <span class="token string">'MerchantLimitsChecked'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> updated <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> merchantLimits<span class="token operator">:</span> <span class="token punctuation">{</span> withinLimits<span class="token operator">:</span> event<span class="token punctuation">.</span>withinLimits<span class="token punctuation">,</span> dailyRemaining<span class="token operator">:</span> event<span class="token punctuation">.</span>dailyRemaining<span class="token punctuation">,</span> checkedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">tryCompleteVerification</span><span class="token punctuation">(</span>updated<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'FraudScoreCalculated'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> current<span class="token punctuation">.</span>fraudAssessment <span class="token operator">&amp;&amp;</span> event<span class="token punctuation">.</span>calculatedAt <span class="token operator">&lt;=</span> current<span class="token punctuation">.</span>fraudAssessment<span class="token punctuation">.</span>assessedAt <span class="token punctuation">)</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token keyword">const</span> updated <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> fraudAssessment<span class="token operator">:</span> <span class="token punctuation">{</span> score<span class="token operator">:</span> event<span class="token punctuation">.</span>score<span class="token punctuation">,</span> riskLevel<span class="token operator">:</span> event<span class="token punctuation">.</span>riskLevel<span class="token punctuation">,</span> assessedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>calculatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> event<span class="token punctuation">.</span>calculatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">tryCompleteVerification</span><span class="token punctuation">(</span>updated<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>And:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">tryCompleteVerifications</span><span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification<span class="token punctuation">,</span> event<span class="token operator">:</span> PaymentVerificationEvent <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token operator">|</span> PaymentVerification <span class="token operator">|</span> <span class="token punctuation">{</span> document<span class="token operator">:</span> PaymentVerification<span class="token punctuation">;</span> events<span class="token operator">:</span> VerificationEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> <span class="token comment">// Ignore if we already made decision</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current<span class="token punctuation">.</span>decision<span class="token punctuation">)</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token comment">// Check if we now have BOTH critical pieces</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>current<span class="token punctuation">.</span>fraudAssessment <span class="token operator">||</span> <span class="token operator">!</span>current<span class="token punctuation">.</span>merchantLimits<span class="token punctuation">)</span> <span class="token comment">// Don’t have both yet - stay in processing</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'processing'</span><span class="token punctuation">,</span> dataQuality<span class="token operator">:</span> <span class="token string">'processing'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> decision <span class="token operator">=</span> current<span class="token punctuation">.</span>fraudAssessment<span class="token punctuation">.</span>riskLevel <span class="token operator">===</span> <span class="token string">'high'</span> <span class="token operator">?</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">'declined'</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">'High fraud risk'</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token operator">!</span>current<span class="token punctuation">.</span>merchantLimits<span class="token punctuation">.</span>withinLimits <span class="token operator">?</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">'declined'</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">'High fraud risk'</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">'approved'</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">'Verified'</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> document<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> status<span class="token operator">:</span> decision<span class="token punctuation">.</span>approval<span class="token punctuation">,</span> decision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> events<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'PaymentVerificationCompleted'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> decision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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?</p> <h2 id="how-to-know-what-we-dont-know" style="position:relative;"><a href="#how-to-know-what-we-dont-know" aria-label="how to know what we dont know permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How to know what we don’t know?</h2> <p>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:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>We see here that someone added the first Pizza, then maybe accidentally added it again, corrected their mistake, and confirmed the order.</p> <p>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:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>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.</p> <p>Can we then proceed? Maybe yes and maybe no. For this particular order, it’d be correct, but what if our real order:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>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?</p> <p>For instance, in such a delivery case:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>Let’s discuss a few strategies to deal with that!</p> <h2 id="external-vs-internal-events" style="position:relative;"><a href="#external-vs-internal-events" aria-label="external vs internal events permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>External vs Internal events</h2> <blockquote> <ul> <li>Doctor, it hurts when I bend my arm this way.</li> <li>Then don’t bend it this way</li> </ul> </blockquote> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 550px; height: auto" > <a class="gatsby-resp-image-link" href="/static/02ac7637b515b37e804b282785aaa420/021b5/2025-11-03-pun-small.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url('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'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="doctor" title="doctor" src="/static/02ac7637b515b37e804b282785aaa420/021b5/2025-11-03-pun-small.jpg" srcset="/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" sizes="(max-width: 550px) 100vw, 550px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>One of the most common mistakes we learn too late is separating our events into internal and external (or private and public). <a href="/en/events_should_be_as_small_as_possible/">Internal information can and should be more granular</a>. We need it to be precise in capturing the business context and making our decision.</p> <p>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.</p> <p>So in our example, if we published to the outside world just:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">CartConfirmed { cartId: 1, items: [ { name: "Spaghetti Carbonara" } ], confirmedAt: "2025-11-03 11:44:27" }</code></pre></div> <p>Such a type of event is also called a <strong>Summary Event</strong>. We should not mistake it with <em>the latest state</em>. 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 <a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a>.</p> <p>We can define such an internal event API as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">ItemAddedToCart</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'sc:int:ItemAddedToCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ItemRemovedFromCart</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'sc:int:ItemRemovedFromCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">CartConfirmed</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'sc:int:CartConfirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> ItemAddedToCart <span class="token operator">|</span> ItemRemovedFromCart <span class="token operator">|</span> CartConfirmed<span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ProductItem</span> <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>and public as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">CartOpened</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'sc:ext:CartOpened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">CartConfirmed</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'sc:ext:CartConfirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartExternalEvent</span> <span class="token operator">=</span> CartOpened <span class="token operator">|</span> CartConfirmed<span class="token punctuation">;</span></code></pre></div> <p>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 <a href="/en/how_to_do_event_versioning/">minimising the need for versioning when flow changes</a>.</p> <p>Ok, but how to map internal events into external?</p> <p>We can enrich them using such a function:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> ShoppingCartExternalEvent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./shoppingCart.external'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> ShoppingCart<span class="token punctuation">,</span> ShoppingCartEvent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./shoppingCart.internal'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> enrich <span class="token operator">=</span> <span class="token punctuation">(</span> event<span class="token operator">:</span> ShoppingCartEvent<span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartExternalEvent <span class="token operator">|</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'sc:int:ItemAddedToCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> state <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">?</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'sc:ext:CartOpened'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>cartId<span class="token punctuation">,</span> openedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'sc:int:CartConfirmed'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'sc:ext:CartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>cartId<span class="token punctuation">,</span> productItems<span class="token operator">:</span> state<span class="token operator">?.</span>productItems <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>confirmedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p><strong>If we’re using messaging, this means also separating queues/topics.</strong> 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.:</p> <ul> <li>‘carts:events:int’</li> <li>‘carts:events:out’.</li> </ul> <p>Similarly, for RabbitMQ or similar tools, you should have separate queues for internal and external communications.</p> <p>This is important, as you can now:</p> <ul> <li>Publish messages that other modules need, decreasing the number of issues with ordering,</li> <li>Have enrichment as an anti-corruption layer for your internal process changes,</li> <li>You can have different scaling capabilities for internal and external events. Maybe for internal, you don’t even need a messaging system, maybe <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">outbox or event store subscriptions</a> will be enough? Maybe you could cut costs using AWS SQS for internal communication and AWS Kinesis for cross-module?</li> <li>You can now define different security for those topics and retention policies.</li> </ul> <p>Sweet, right?</p> <h2 id="its-not-me-its-them" style="position:relative;"><a href="#its-not-me-its-them" aria-label="its not me its them permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>It’s not me, it’s them</h2> <p>Maybe it’s sweet enough for you, but you may also say:</p> <blockquote> <p>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.</p> </blockquote> <p>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.</p> <p>The first idea could be: Let’s add timestamps!</p> <p>Let’s see how it looks for our example:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>And the out of order delivery:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>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 <a href="https://en.wikipedia.org/wiki/Clock_drift">clock drift</a>.</p> <p>What we actually need is the logical clock. One that increments after each operation. So something like:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>If we had such, then our delivery would look as follows:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>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.</p> <p>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.</p> <p>In this case, we got the events in the following order: 2, 1, 5, 6.</p> <p>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?</p> <p>What if we kept a list of pending events in our data model? Let’s try that!</p> <p>Our kitchen order could look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">KitchenOrder</span> <span class="token operator">=</span> <span class="token punctuation">{</span> orderId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> status<span class="token operator">:</span> <span class="token string">'Incomplete'</span> <span class="token operator">|</span> <span class="token string">'Ready'</span> <span class="token operator">|</span> <span class="token string">'InPreparation'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">KitchenOrderCommand</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddItem'</span> <span class="token operator">|</span> <span class="token string">'RemoveItem'</span><span class="token punctuation">;</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Confirm'</span><span class="token punctuation">;</span> orderId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ProductItem</span> <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>When storing it, we could store it with additional metadata:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">DocumentWithPendingCommands<span class="token operator">&lt;</span>State<span class="token punctuation">,</span> Command<span class="token operator">></span></span> <span class="token operator">=</span> State <span class="token operator">&amp;</span> <span class="token punctuation">{</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> lastProcessedRevision<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> pendingCommands<span class="token operator">:</span> PendingCommand<span class="token operator">&lt;</span>Command<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>As you see, besides the regular data, we have two other properties: pending commands and last processed revision.</p> <p>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.</p> <p>The code for that workflow could look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">handle</span><span class="token punctuation">(</span> event<span class="token operator">:</span> ShoppingCartEvent<span class="token punctuation">,</span> document<span class="token operator">:</span> DocumentWithPendingCommands<span class="token operator">&lt;</span> KitchenOrder<span class="token punctuation">,</span> KitchenOrderCommand <span class="token operator">></span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> DocumentWithPendingCommands<span class="token operator">&lt;</span>KitchenOrder<span class="token punctuation">,</span> KitchenOrderCommand<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> metadata<span class="token punctuation">,</span> <span class="token operator">...</span>data <span class="token punctuation">}</span> <span class="token operator">=</span> document <span class="token operator">??</span> <span class="token punctuation">{</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> lastProcessedRevision<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> pendingCommands<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> state<span class="token operator">:</span> KitchenOrder <span class="token operator">=</span> <span class="token punctuation">{</span> orderId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>cartId<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'Incomplete'</span><span class="token punctuation">,</span> <span class="token operator">...</span>data<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> updated <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>state<span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> lastProcessedRevision<span class="token operator">:</span> metadata<span class="token punctuation">.</span>lastProcessedRevision<span class="token punctuation">,</span> pendingCommands<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token operator">...</span>metadata<span class="token punctuation">.</span>pendingCommands<span class="token punctuation">,</span> <span class="token function">mapToPendingCommand</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">handlePendingCommands</span><span class="token punctuation">(</span>updated<span class="token punctuation">,</span> decide<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">mapToPendingCommand</span><span class="token punctuation">(</span> event<span class="token operator">:</span> ShoppingCartEvent<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> PendingCommand<span class="token operator">&lt;</span>KitchenOrderCommand<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'sc:int:ItemAddedToCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddItem'</span><span class="token punctuation">,</span> productId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> revision<span class="token operator">:</span> event<span class="token punctuation">.</span>metadata<span class="token operator">?.</span>revision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'sc:int:ItemRemovedFromCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'RemoveItem'</span><span class="token punctuation">,</span> productId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> revision<span class="token operator">:</span> event<span class="token punctuation">.</span>metadata<span class="token operator">?.</span>revision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'sc:int:CartConfirmed'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Confirm'</span><span class="token punctuation">,</span> orderId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>cartId<span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> revision<span class="token operator">:</span> event<span class="token punctuation">.</span>metadata<span class="token operator">?.</span>revision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>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.</p> <p>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.</p> <p>Let’s see what processing pending items looks like:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token generic-function"><span class="token function">handlePendingCommands</span><span class="token generic class-name"><span class="token operator">&lt;</span>State<span class="token punctuation">,</span> Command<span class="token operator">></span></span></span><span class="token punctuation">(</span> document<span class="token operator">:</span> DocumentWithPendingCommands<span class="token operator">&lt;</span>State<span class="token punctuation">,</span> Command<span class="token operator">></span><span class="token punctuation">,</span> <span class="token function-variable function">decide</span><span class="token operator">:</span> <span class="token punctuation">(</span>command<span class="token operator">:</span> Command<span class="token punctuation">,</span> state<span class="token operator">:</span> State<span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> DocumentWithPendingCommands<span class="token operator">&lt;</span>State<span class="token punctuation">,</span> Command<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> metadata<span class="token punctuation">,</span> <span class="token operator">...</span>data <span class="token punctuation">}</span> <span class="token operator">=</span> document<span class="token punctuation">;</span> <span class="token keyword">const</span> commandsToHandle <span class="token operator">=</span> <span class="token function">getCommandsReadyToHandle</span><span class="token punctuation">(</span> metadata<span class="token punctuation">.</span>pendingCommands<span class="token punctuation">,</span> metadata<span class="token punctuation">.</span>lastProcessedRevision<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Nothing to do see here, please disperse</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>commandsToHandle<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> document<span class="token punctuation">;</span> <span class="token keyword">let</span> state <span class="token operator">=</span> data <span class="token keyword">as</span> State<span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> command <span class="token keyword">of</span> commandsToHandle<span class="token punctuation">)</span> <span class="token punctuation">{</span> state <span class="token operator">=</span> <span class="token function">decide</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> lastCommand <span class="token operator">=</span> commandsToHandle<span class="token punctuation">[</span>commandsToHandle<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>state<span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> lastProcessedRevision<span class="token operator">:</span> lastCommand<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>revision<span class="token punctuation">,</span> pendingCommands<span class="token operator">:</span> metadata<span class="token punctuation">.</span>pendingCommands<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span> <span class="token punctuation">(</span>a<span class="token punctuation">)</span> <span class="token operator">=></span> a<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>revision <span class="token operator">></span> lastCommand<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>revision<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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:</p> <ul> <li>We can process actions 1, 2, 3, 4</li> <li>Action 6 remains, as we’re missing 5.</li> </ul> <p>The filtering can look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token generic-function"><span class="token function">getCommandsReadyToHandle</span><span class="token generic class-name"><span class="token operator">&lt;</span>Command<span class="token operator">></span></span></span><span class="token punctuation">(</span> pending<span class="token operator">:</span> PendingCommand<span class="token operator">&lt;</span>Command<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> lastProcessedRevision<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> PendingCommand<span class="token operator">&lt;</span>Command<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> pending <span class="token comment">// filter out commands that have already been processed</span> <span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span>cmd<span class="token punctuation">)</span> <span class="token operator">=></span> cmd<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>revision <span class="token operator">></span> lastProcessedRevision<span class="token punctuation">)</span> <span class="token comment">// sort by revision to ensure correct order</span> <span class="token punctuation">.</span><span class="token function">sort</span><span class="token punctuation">(</span><span class="token punctuation">(</span>a<span class="token punctuation">,</span> b<span class="token punctuation">)</span> <span class="token operator">=></span> a<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>revision <span class="token operator">-</span> b<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>revision<span class="token punctuation">)</span> <span class="token comment">// only take commands that are consecutive in terms of revision</span> <span class="token punctuation">.</span><span class="token generic-function"><span class="token function">reduce</span><span class="token generic class-name"><span class="token operator">&lt;</span>PendingCommand<span class="token operator">&lt;</span>Command<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>acc<span class="token punctuation">,</span> command<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> lastRevision <span class="token operator">=</span> acc<span class="token punctuation">[</span>acc<span class="token punctuation">.</span>length <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">]</span><span class="token operator">?.</span>metadata<span class="token punctuation">.</span>revision<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">!</span>lastRevision <span class="token operator">||</span> command<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>revision <span class="token operator">===</span> lastRevision <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">?</span> <span class="token punctuation">[</span><span class="token operator">...</span>acc<span class="token punctuation">,</span> command<span class="token punctuation">]</span> <span class="token operator">:</span> acc<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">decide</span><span class="token punctuation">(</span> command<span class="token operator">:</span> KitchenOrderCommand<span class="token punctuation">,</span> order<span class="token operator">:</span> KitchenOrder<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> KitchenOrder <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>command<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'AddItem'</span><span class="token operator">:</span> <span class="token keyword">case</span> <span class="token string">'RemoveItem'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>order<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Incomplete'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> order<span class="token punctuation">;</span> <span class="token keyword">const</span> updatedItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map</span><span class="token punctuation">(</span> order<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span>item<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> item<span class="token punctuation">.</span>quantity<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> current <span class="token operator">=</span> updatedItems<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>productId<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">const</span> multiplier <span class="token operator">=</span> command<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'AddItem'</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">const</span> updated <span class="token operator">=</span> current <span class="token operator">+</span> multiplier <span class="token operator">*</span> command<span class="token punctuation">.</span>quantity<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>updated <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> updatedItems<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> updated<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> updatedItems<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>order<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token builtin">Array</span><span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>updatedItems<span class="token punctuation">.</span><span class="token function">entries</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token punctuation">[</span>productId<span class="token punctuation">,</span> quantity<span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'Confirm'</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>order<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Incomplete'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> order<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>order<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'Ready'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">KitchenOrderCommand</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddItem'</span> <span class="token operator">|</span> <span class="token string">'RemoveItem'</span><span class="token punctuation">;</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Confirm'</span><span class="token punctuation">;</span> orderId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>Not that big a hassle as it may seem, but…</p> <h2 id="whats-revision-and-how-to-get-it" style="position:relative;"><a href="#whats-revision-and-how-to-get-it" aria-label="whats revision and how to get it permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What’s revision and how to get it?</h2> <p>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:</p> <ul> <li><a href="/en/optimistic_concurrency_for_pessimistic_times/">Optimistic concurrency for pessimistic times</a></li> <li><a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">How to use ETag header for optimistic concurrency</a>.</li> </ul> <p>Essentially, if you’re:</p> <ul> <li>using a typical implementation of optimistic concurrency where each record/document change increments its version/revision,</li> <li>publishing an event after each business operation.</li> </ul> <p>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 <a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">Dealing with Eventual Consistency and Idempotency in projections</a>.</p> <blockquote> <p>But Oskar, what if I have more than one record?</p> </blockquote> <p>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.</p> <p>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.</p> <p>Read more on why in:</p> <ul> <li><a href="https://www.architecture-weekly.com/p/how-does-kafka-know-what-was-the">How does Kafka know what was the last message it processed? Deep dive into Offset Tracking</a>,</li> <li><a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>,</li> <li><a href="/en/ordering_in_postgres_outbox/">How Postgres sequences issues can impact your messaging guarantees</a>.</li> </ul> <p>Then you’re back to square one, and the <a href="https://www.architecture-weekly.com/p/dealing-with-race-conditions-in-event">previous article</a>.</p> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>Proper modelling in Event-Driven Architecture can spare you a lot of complicated implementation tricks.</p> <p>If you</p> <ul> <li>define essential events for your process,</li> <li>ensure that they have completeness of information,</li> <li>shape contracts and communication between modules, respecting the internal and external split.</li> </ul> <p>Then, when things get easier to handle, we can define conditions that tell us when to take action.</p> <p>Still, sometimes we may:</p> <ul> <li>have strict ordering needs,</li> <li>be using a queue that doesn’t give us an ordering guarantee,</li> <li>need to adjust to the other teams.</li> </ul> <p>Then, using revision can be a decent option to solve things in an organised way.</p> <p><strong>If you’re dealing with such issues, I’m happy to help you through consulting or mentoring. <a href="mailto:[email protected]">Contact me</a> and we’ll find a way to unblock you!</strong></p> <p>Read also more in:</p> <ul> <li><a href="/en/dealing_with_race_conditions_in_eda_using_read_models/">Dealing with Race Conditions in Event-Driven Architecture with Read Models</a></li> <li><a href="https://www.architecture-weekly.com/p/the-order-of-things-why-you-cant">The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems</a>,</li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a>,</li> <li><a href="/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/">Dealing with Eventual Consistency and Idempotency in MongoDB projections</a></li> <li><a href="/en/saga_process_manager_distributed_transactions/">Saga and Process Manager - distributed processes in practice</a>,</li> <li><a href="https://www.architecture-weekly.com/p/predictable-identifiers-enabling">Predictable Identifiers: Enabling True Module Autonomy in Distributed Systems</a></li> <li><a href="https://www.architecture-weekly.com/p/dealing-with-eventual-consistency">Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers</a>,</li> <li><a href="/en/event_driven_distributed_processes_by_example/">Event-driven distributed processes by example</a>,</li> <li><a href="https://www.architecture-weekly.com/p/workflow-engine-design-proposal-tell">Workflow Engine design proposal, tell me your thoughts</a>,</li> <li><a href="/en/how_to_have_fun_with_typescript_and_workflow/">How TypeScript can help in modelling business workflows</a>,</li> <li><a href="/en/how_to_update_past_data_in_event_sourcing/">Oops I did it again, or how to update past data in Event Sourcing</a>,</li> <li><a href="/en/event_transformations_and_loosely_coupling/">Event transformations, a tool to keep our processes loosely coupled</a>,</li> <li><a href="/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/">Testing asynchronous processes with a little help from .NET Channels</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Dealing with Race Conditions in Event-Driven Architecture with Read Models]]>https://event-driven.io/en/dealing_with_race_conditions_in_eda_using_read_models/https://event-driven.io/en/dealing_with_race_conditions_in_eda_using_read_models/<blockquote> <p>My events came out of order! What should I do?!</p> </blockquote> <p><strong>Are you familiar with the term “phantom record” and its benefits? No? Let me explain it to you today.</strong></p> <p><strong>Everyone has a plan until they get punched in the mouth.</strong> 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.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6852c645507e912e86625c5444dafe9f/b09c1/tyson.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADhklEQVQ4yx3H2U/bBQDA8V/QuXAoDmQcW6GVMcpNf/DrSfvrBaVY+PUCCi0FCh1Qjl4b51o2ZOAOs7kZj4QYH0RjlpktOmZ88l8wvvriizG++2i+Jnv65CMYZRcmmxOD1YneYsciOxEtMjqTjX7DAH16C07PB/jDUwQmooQmYijByOu7vQojyjiOQR9t3Xokk4xgsMjozTYkkw2DWSYxqZCNBUhM+rGYBtCJBhzOIXZ2C2xv77G9fZONTJ7Do7ssJFPsFQ9e26LVIeptCDqDhQGrjF22YzOb+Ww5wmk2xuPlCHpRQuztxy67WFvLEI8nyF/fYmZ2gel4gsBEjMFhhUx+i9RGDrPNjaDVGenSm3C67Hx5d5eHG3EKIQ9f5eeIBkaRunvxDnlJpdaZmZlnd7dAfG6RaGyepZV14rOLRCanGRlR0PWZEYYHrORiIV49KfDPj0/YnBrDZZA4SYzx94tHnNzJsR0f5/FBkXxinkImw9HNAjc3t9jM5Nnb3GHIMYSmsZUenQnBYRpgf3GaP7++zW+HSZ4uBXl1I8p3SwFeXI/yvJhkbjzIJ8eH7ORzbObyrK2uc3zvAcnUBsmNHMP+CdQtXYg6I8JfqXlO4+M8XAjyS3qSZ9f8PE+F2A+4eBD18mtxHofFisc1jNvmwj/kQ3F5SYQjzPv8bIcnSPsUgqLErN2F8N/oCCd9/aiudpAec/J51MuSw4Spo5OPQoP88WidouLhwDLAPZtMwWBmQ5SY1nYS1nYTvNKOqNaiatRis7sRkmYLCb2RMYOF76cVXka83Al7WPHY+DTm5dmyQmZqkp9fnvHtN6esZW+wuf8hxeP7PP3pjNhKmrKaJt5TtaFp0yOM9RsxtuuYlgz8GxqFmRC///AFp0dZbisyXklE3dpNm85IR68RzdVuVM0dXNZ0omkVqbx4hXdrm3nrHRXlVc0Ib9e/T0m1ikFR4swus9/Zy5jDxcWmdqobtTQ0aykpr+VcRR1lVU1cqG+hXt1Jc4dEa48Ro93DSGCCyXiC5XQWoaG5i5YuPSbZQUtrFyWVjQillzh/oYnyGg1W9yCr2TRL6XUW1laJX1skkVoilcmwkskSW0jiC0Xotw4hWT0IW7ducfTxffzhEBU1GiqqNbxZoaKvp5+Qz41z2E1br4kmrUitupPKuhZKq9SUlDUgnKtBOF/DG2V1lFZe4rKmnf8BTlL3+kT1s7kAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="tyson" title="tyson" src="/static/6852c645507e912e86625c5444dafe9f/b09c1/tyson.png" srcset="/static/6852c645507e912e86625c5444dafe9f/36ca5/tyson.png 200w, /static/6852c645507e912e86625c5444dafe9f/a3397/tyson.png 400w, /static/6852c645507e912e86625c5444dafe9f/b09c1/tyson.png 626w" sizes="(max-width: 626px) 100vw, 626px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>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.</p> <p>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.</p> <p>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.</p> <p>And the sad part is that it’s our role to provide this efficiency. Yelling at event-driven clouds won’t help.</p> <h2 id="race-conditions-in-eda" style="position:relative;"><a href="#race-conditions-in-eda" aria-label="race conditions in eda permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Race Conditions in EDA</h2> <p>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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p>Out of order issues may also happen when you’re using tools like SQS or Google PubSub, which only guarantee best-effort ordering.</p> <p>Or when your outbox pattern deletes processed messages and loses sequence.</p> <p>Or when network delays shuffle carefully ordered streams.</p> <p>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.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c94972a72335077c07d38c1710f453d7/2977d/race-condition.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 54.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACVElEQVQoz22RW2sTQRiG86PFCy+80BsvBRWsiMUb0YJSSuuJigml1HrANGnTpKecd3Z2d3ZnD9nsIZtHMtULxYGXGYaZ93u+96sBKC9ir9Hl6uyKsFtHuxY6jIjjmEjZ6E6dUDnEyYwkic39v4qiiDRNqa0MrUnA07UGJ99a+G/vIC9buL5GpyVq1MXZvE0WCPJFRVkU/G9VVXVt2Dz6yeXFiC/7V/S6fWLtIR2Hcf8c0W5gjfpIMcUWAs9zkVIihEAp9Zc8zyNJEmqvNl5yeNBifa3OcatvqpVLsM+bWBs3CeWIfLGkKEtmecmiqsjzzNCsqBaLhVFRFMxmM2ofPrzn5PiMw/0+ve6AMAzw/QBpCxwxxppOELZE2VPk6QHWZIjWoSFdkQXB6r1vzoZwa2uTr4dt1p/UaR1dGsKiKE21RbUkzXKyckkw7GC/vkUkR+g4Ifk9iD+EeZ5fE+7sbNM6OmXvc4feaZ8w1KaibdsEysUZ9pC2he+5KDEiUMpk6Lkuk/EYKe3fhJ6Zdq2qSgZ9l8cPPtFuDYClyWNFpcZn9F/cQFuXxPOC2TwzGc6zjCzPjdL5nHSemT+m5VWLkRtw3rpiOpiQ6IDI84j9AC0FwUWTUFp4wkK7DsoW+EIQug6+lEbKmpo/aRRdGyZ6wMn3+4hJk7KEPJuTZxlFnpuJZ4HN8M1d9I9NnN2HeI1nuHvPcXcfEbffY2/fYzbtkmbFtWEcCk6aG0xGHdI0J05ig5+YfUYcuIgf7/DOv+O0P6O6+6jeAX6ngR60cZof0XJkIvkFso4uqJFbbz0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="race condition" title="race condition" src="/static/c94972a72335077c07d38c1710f453d7/a331c/race-condition.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="events-vs-rumours" style="position:relative;"><a href="#events-vs-rumours" aria-label="events vs rumours permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Events vs Rumours?</h2> <p>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.</p> <p>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.</p> <p>We’re saying that in the event-driven world, events are facts. But that’s just half the truth.</p> <p>Those events that we store/publish are facts for us, or at least represent the current state of our knowledge.</p> <p><strong>The events from external systems are rumours at best. We need to interpret them to make them (our) facts.</strong></p> <p>Ok, but what can we do? Sit and cry?</p> <p>Of course, we can do more. We’ll discuss today a simple technique with Read Models that can take you far enough.</p> <p>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.</p> <p>Event Sourcing assumes you can rebuild state by replaying events in sequence. When <em>FraudScoreCalculated</em> arrives before <em>PaymentInitiated</em>, 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?</p> <p>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 <em>FraudScoreCalculated</em> looks for a payment document to update. No document exists. The update fails.</p> <p>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.</p> <p>Store data as it arrives and “denoise” on your side. Interpret them and save your own “facts”. Acknowledge the need for a <a href="/en/internal_external_events/">split between internal and external events</a>. Use an Anti-Corruption Layer (ACL) pattern to protect from external chaos.</p> <h2 id="read-models-as-anti-corruption-layer" style="position:relative;"><a href="#read-models-as-anti-corruption-layer" aria-label="read models as anti corruption layer permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Read models as Anti-Corruption Layer</h2> <p>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.</p> <p>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.</p> <p>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.</p> <p>We could define events using TypeScript, as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">PaymentVerificationEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> PaymentInitiated <span class="token operator">|</span> FraudScoreCalculated <span class="token operator">|</span> RiskAssessmentCompleted <span class="token operator">|</span> MerchantLimitsChecked <span class="token operator">|</span> PaymentCompleted <span class="token operator">|</span> PaymentDeclined<span class="token punctuation">;</span></code></pre></div> <p>Having</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">PaymentInitiated</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"PaymentInitiated"</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> amount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> currency<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> gatewayId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> initiatedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">FraudScoreCalculated</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"FraudScoreCalculated"</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> score<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> riskLevel<span class="token operator">:</span> <span class="token string">"low"</span> <span class="token operator">|</span> <span class="token string">"medium"</span> <span class="token operator">|</span> <span class="token string">"high"</span><span class="token punctuation">;</span> calculatedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">RiskAssessmentCompleted</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"RiskAssessmentCompleted"</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> riskScore<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> factors<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> assessedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">MerchantLimitsChecked</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"MerchantLimitsChecked"</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> withinLimits<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> dailyRemaining<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> checkedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">PaymentCompleted</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"PaymentCompleted"</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> completedAt<span class="token operator">:</span> Date <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">PaymentDeclined</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"PaymentDeclined"</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> declinedAt<span class="token operator">:</span> Date <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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)</code></pre></div> <p>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.</p> <p>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.</p> <p>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.</p> <p>It can look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">PaymentVerification</span> <span class="token operator">=</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> payment<span class="token operator">?</span><span class="token operator">:</span> Payment<span class="token punctuation">;</span> fraudAssessment<span class="token operator">?</span><span class="token operator">:</span> FraudAssessment<span class="token punctuation">;</span> riskEvaluation<span class="token operator">?</span><span class="token operator">:</span> RiskEvaluation<span class="token punctuation">;</span> merchantLimits<span class="token operator">?</span><span class="token operator">:</span> MerchantLimits<span class="token punctuation">;</span> decision<span class="token operator">?</span><span class="token operator">:</span> Decision<span class="token punctuation">;</span> status<span class="token operator">:</span> <span class="token string">"unknown"</span> <span class="token operator">|</span> <span class="token string">"processing"</span> <span class="token operator">|</span> <span class="token string">"approved"</span> <span class="token operator">|</span> <span class="token string">"declined"</span><span class="token punctuation">;</span> completionPercentage<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> lastUpdated<span class="token operator">:</span> Date<span class="token punctuation">;</span> dataQuality<span class="token operator">:</span> <span class="token string">"partial"</span> <span class="token operator">|</span> <span class="token string">"sufficient"</span> <span class="token operator">|</span> <span class="token string">"complete"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Payment</span> <span class="token operator">=</span> <span class="token punctuation">{</span> amount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> currency<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> gatewayId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> initiatedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">FraudAssessment</span> <span class="token operator">=</span> <span class="token punctuation">{</span> score<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> riskLevel<span class="token operator">:</span> <span class="token string">"low"</span> <span class="token operator">|</span> <span class="token string">"medium"</span> <span class="token operator">|</span> <span class="token string">"high"</span><span class="token punctuation">;</span> assessedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">RiskEvaluation</span> <span class="token operator">=</span> <span class="token punctuation">{</span> score<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> factors<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> assessedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">MerchantLimits</span> <span class="token operator">=</span> <span class="token punctuation">{</span> withinLimits<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> dailyRemaining<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> checkedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Decision</span> <span class="token operator">=</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">"approved"</span> <span class="token operator">|</span> <span class="token string">" declined"</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> decidedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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.</p> <p>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.</p> <p>It can look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">evolve</span> <span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification<span class="token punctuation">,</span> event<span class="token operator">:</span> PaymentVerificationEvent <span class="token punctuation">)</span><span class="token operator">:</span> PaymentVerification <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token punctuation">{</span> current <span class="token operator">=</span> current <span class="token operator">??</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> event<span class="token punctuation">.</span>paymentId<span class="token punctuation">,</span> initialState<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">"PaymentInitiated"</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onPaymentInitiated</span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">"FraudScoreCalculated"</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onFraudScoreCalculated</span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">"RiskAssessmentCompleted"</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onRiskAssessmentCompleted</span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">"MerchantLimitsChecked"</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onMerchantLimitsChecked</span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">"PaymentCompleted"</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onPaymentCompleted</span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">"PaymentDeclined"</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onPaymentDeclined</span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> initialState<span class="token operator">:</span> PaymentVerification <span class="token operator">=</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token operator">!</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'unknown'</span><span class="token punctuation">,</span> completionPercentage<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> dataQuality<span class="token operator">:</span> <span class="token string">'partial'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// Using Emmett it could be defined as</span> <span class="token keyword">export</span> <span class="token keyword">const</span> paymentVerificationProjection <span class="token operator">=</span> <span class="token function">pongoSingleStreamProjection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token comment">// collection to where we store projection</span> collectionName<span class="token operator">:</span> <span class="token string">'paymentVerification'</span><span class="token punctuation">,</span> <span class="token function-variable function">getDocumentId</span><span class="token operator">:</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=></span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>paymentId<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token string">'PaymentInitiated'</span><span class="token punctuation">,</span> <span class="token string">'FraudScoreCalculated'</span><span class="token punctuation">,</span> <span class="token string">'RiskAssessmentCompleted'</span><span class="token punctuation">,</span> <span class="token string">'MerchantLimitsChecked'</span><span class="token punctuation">,</span> <span class="token string">'PaymentApproved'</span><span class="token punctuation">,</span> <span class="token string">'PaymentDeclined'</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// use this state when there's no document in collection of certain id</span> <span class="token function-variable function">initialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> initialState<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And yes, even if the data is not in the state we expect, we can always create the <em><strong>Phantom Record</strong></em> that represents the best state we can in the conditions we have.</p> <p>What if fraud scoring arrives first?</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">onFraudScoreCalculated</span><span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification<span class="token punctuation">,</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> FraudScoreCalculated<span class="token punctuation">)</span><span class="token operator">:</span> PaymentVerification <span class="token punctuation">{</span> <span class="token comment">// Ignore event if we're already ahead of it</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current<span class="token punctuation">.</span>fraudAssessment <span class="token operator">&amp;&amp;</span> event<span class="token punctuation">.</span>calculatedAt <span class="token operator">&lt;=</span> current<span class="token punctuation">.</span>fraudAssessment<span class="token punctuation">.</span>assessedAt<span class="token punctuation">)</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token keyword">const</span> updated <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> fraudAssessment<span class="token operator">:</span> <span class="token punctuation">{</span> score<span class="token operator">:</span> event<span class="token punctuation">.</span>score<span class="token punctuation">,</span> riskLevel<span class="token operator">:</span> event<span class="token punctuation">.</span>riskLevel<span class="token punctuation">,</span> assessedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>calculatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> event<span class="token punctuation">.</span>calculatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>riskLevel <span class="token operator">!==</span> <span class="token string">'high'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>updated<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'declined'</span><span class="token punctuation">,</span> decision<span class="token operator">:</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">'declined'</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">High fraud risk detected: score </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>event<span class="token punctuation">.</span>score<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>calculatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> updated<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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 <a href="https://martinfowler.com/articles/patterns-of-distributed-systems/lamport-clock.html">logical timestamp</a>, then we need to live with what we have. Here, we’re assuming that <em>FraudScoreCalculated</em> 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.</p> <p>Now, lets have a look on the payment initiation when fraud scoring might have already completed:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">onPaymentInitiated</span><span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification<span class="token punctuation">,</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> PaymentInitiated<span class="token punctuation">)</span><span class="token operator">:</span> PaymentVerification <span class="token punctuation">{</span> <span class="token comment">// Ignore if we already handled this event</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current<span class="token punctuation">.</span>payment<span class="token punctuation">)</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> payment<span class="token operator">:</span> <span class="token punctuation">{</span> amount<span class="token operator">:</span> event<span class="token punctuation">.</span>amount<span class="token punctuation">,</span> currency<span class="token operator">:</span> event<span class="token punctuation">.</span>currency<span class="token punctuation">,</span> gatewayId<span class="token operator">:</span> event<span class="token punctuation">.</span>gatewayId<span class="token punctuation">,</span> initiatedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>initiatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> event<span class="token punctuation">.</span>initiatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>If fraud scoring was completed first, <em>current</em> already has fraud data. The handler merges payment details into the current state.</p> <p>We can also mark that in the field that I called <em>dataQuality</em>. 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.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">onPaymentCompleted</span><span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification<span class="token punctuation">,</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> PaymentCompleted<span class="token punctuation">)</span><span class="token operator">:</span> PaymentVerification <span class="token punctuation">{</span> <span class="token comment">// Ignore if we already made decision</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current<span class="token punctuation">.</span>decision<span class="token punctuation">)</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token keyword">const</span> decision <span class="token operator">=</span> current<span class="token punctuation">.</span>fraudAssessment<span class="token operator">?.</span>riskLevel <span class="token operator">===</span> <span class="token string">'high'</span> <span class="token operator">?</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">'declined'</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Approval attempted but overridden by fraud (score: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>current<span class="token punctuation">.</span>fraudAssessment<span class="token punctuation">.</span>score<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>approvedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">'approved'</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Approved by </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>event<span class="token punctuation">.</span>approvedBy<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>approvedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> status<span class="token operator">:</span> decision<span class="token punctuation">.</span>approval<span class="token punctuation">,</span> decision<span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> event<span class="token punctuation">.</span>approvedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="waiting-for-dependencies" style="position:relative;"><a href="#waiting-for-dependencies" aria-label="waiting for dependencies permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Waiting for Dependencies</h2> <p>Some decisions need multiple pieces. The <em>MerchantLimitsChecked</em> 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.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">onMerchantLimitsChecked</span><span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification<span class="token punctuation">,</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> MerchantLimitsChecked<span class="token punctuation">)</span><span class="token operator">:</span> PaymentVerification <span class="token punctuation">{</span> <span class="token comment">// Ignore if we already handled this event</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current<span class="token punctuation">.</span>merchantLimits<span class="token punctuation">)</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token keyword">const</span> updated <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> merchantLimits<span class="token operator">:</span> <span class="token punctuation">{</span> withinLimits<span class="token operator">:</span> event<span class="token punctuation">.</span>withinLimits<span class="token punctuation">,</span> dailyRemaining<span class="token operator">:</span> event<span class="token punctuation">.</span>dailyRemaining<span class="token punctuation">,</span> checkedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// Check if we now have BOTH critical pieces</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>updated<span class="token punctuation">.</span>fraudAssessment <span class="token operator">||</span> <span class="token operator">!</span>updated<span class="token punctuation">.</span>merchantLimits<span class="token punctuation">)</span> <span class="token comment">// Don't have both yet - stay in processing</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>updated<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">"processing"</span><span class="token punctuation">,</span> dataQuality<span class="token operator">:</span> <span class="token string">"processing"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// Both present - can make final decision</span> <span class="token keyword">const</span> decision <span class="token operator">=</span> updated<span class="token punctuation">.</span>fraudAssessment<span class="token punctuation">.</span>riskLevel <span class="token operator">===</span> <span class="token string">"high"</span> <span class="token operator">?</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">"declined"</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">"High fraud risk"</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token operator">!</span>updated<span class="token punctuation">.</span>merchantLimits<span class="token punctuation">.</span>withinLimits <span class="token operator">?</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">"declined"</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">"High fraud risk"</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">"approved"</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">"Verified"</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>updated<span class="token punctuation">,</span> status<span class="token operator">:</span> decision<span class="token punctuation">.</span>approval<span class="token punctuation">,</span> decision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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, <em>“we can make the final decision”</em>, but then changed it to <em>“we can make the final update”</em>. Why?</p> <h2 id="projections-acl-responsibility-and-workflows" style="position:relative;"><a href="#projections-acl-responsibility-and-workflows" aria-label="projections acl responsibility and workflows permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Projections, ACL responsibility and Workflows</h2> <p>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…</p> <p>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.</p> <p>What we actually ended up with in the last step is a form of <a href="https://www.architecture-weekly.com/p/workflow-engine-design-proposal-tell">process manager, or workflow</a>, 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.</p> <p>We could define an event as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">PaymentVerificationCompleted</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"PaymentVerificationCompleted"</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">"approved"</span> <span class="token operator">|</span> <span class="token string">" declined"</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> decidedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As we’re making decisions, let’s be frank and rename <em>evolve</em> function to <em>decide</em> and produce a new event when we distilled it from the messy outside world?</p> <p>It could look like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">decide</span><span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> event<span class="token operator">:</span> PaymentVerificationEvent <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token operator">|</span> PaymentVerification <span class="token operator">|</span> <span class="token punctuation">{</span> document<span class="token operator">:</span> PaymentVerification<span class="token punctuation">;</span> events<span class="token operator">:</span> VerificationEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> current <span class="token operator">=</span> current <span class="token operator">??</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> event<span class="token punctuation">.</span>paymentId<span class="token punctuation">,</span> initialState<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// (...) other event handlers</span> <span class="token keyword">case</span> <span class="token string">"MerchantLimitsChecked"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> updated <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> merchantLimits<span class="token operator">:</span> <span class="token punctuation">{</span> withinLimits<span class="token operator">:</span> event<span class="token punctuation">.</span>withinLimits<span class="token punctuation">,</span> dailyRemaining<span class="token operator">:</span> event<span class="token punctuation">.</span>dailyRemaining<span class="token punctuation">,</span> checkedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">tryCompleteVerification</span><span class="token punctuation">(</span>updated<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">"FraudScoreCalculated"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> current<span class="token punctuation">.</span>fraudAssessment <span class="token operator">&amp;&amp;</span> event<span class="token punctuation">.</span>calculatedAt <span class="token operator">&lt;=</span> current<span class="token punctuation">.</span>fraudAssessment<span class="token punctuation">.</span>assessedAt <span class="token punctuation">)</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token keyword">const</span> updated <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> fraudAssessment<span class="token operator">:</span> <span class="token punctuation">{</span> score<span class="token operator">:</span> event<span class="token punctuation">.</span>score<span class="token punctuation">,</span> riskLevel<span class="token operator">:</span> event<span class="token punctuation">.</span>riskLevel<span class="token punctuation">,</span> assessedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>calculatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> lastUpdated<span class="token operator">:</span> event<span class="token punctuation">.</span>calculatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">tryCompleteVerification</span><span class="token punctuation">(</span>updated<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">tryCompleteVerifications</span><span class="token punctuation">(</span> current<span class="token operator">:</span> PaymentVerification<span class="token punctuation">,</span> event<span class="token operator">:</span> PaymentVerificationEvent <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token operator">|</span> PaymentVerification <span class="token operator">|</span> <span class="token punctuation">{</span> document<span class="token operator">:</span> PaymentVerification<span class="token punctuation">;</span> events<span class="token operator">:</span> VerificationEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">{</span> <span class="token comment">// Ignore if we already made decision</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>current<span class="token punctuation">.</span>decision<span class="token punctuation">)</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token comment">// Check if we now have BOTH critical pieces</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>current<span class="token punctuation">.</span>fraudAssessment <span class="token operator">||</span> <span class="token operator">!</span>current<span class="token punctuation">.</span>merchantLimits<span class="token punctuation">)</span> <span class="token comment">// Don't have both yet - stay in processing</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">"processing"</span><span class="token punctuation">,</span> dataQuality<span class="token operator">:</span> <span class="token string">"processing"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> decision <span class="token operator">=</span> current<span class="token punctuation">.</span>fraudAssessment<span class="token punctuation">.</span>riskLevel <span class="token operator">===</span> <span class="token string">"high"</span> <span class="token operator">?</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">"declined"</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">"High fraud risk"</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token operator">!</span>current<span class="token punctuation">.</span>merchantLimits<span class="token punctuation">.</span>withinLimits <span class="token operator">?</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">"declined"</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">"High fraud risk"</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span> approval<span class="token operator">:</span> <span class="token string">"approved"</span><span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">"Verified"</span><span class="token punctuation">,</span> decidedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>checkedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> document<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>current<span class="token punctuation">,</span> status<span class="token operator">:</span> decision<span class="token punctuation">.</span>approval<span class="token punctuation">,</span> decision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> events<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"PaymentVerificationCompleted"</span><span class="token punctuation">,</span> data<span class="token operator">:</span> decision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p><strong>Essentially, we’re making a chaotic outside world linear based on the order of our observations.</strong> We can’t change the outside world, but we can at least know why and where we’ve made our decisions.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5619b5b0e5c0b3cb62d4f3230fa725dc/dd04a/linearity.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 40%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAICAYAAAD5nd/tAAAACXBIWXMAAAsTAAALEwEAmpwYAAABjklEQVQoz5WS228SQRSH+/8/Gh80ktYXU9NqldTKJRsgFRQrbLBFKgHcrWwR20XY0t3Zy8x8ZnfF1kcfvpzLzPnNyZyzpbUGUhRKg9L6r82y93LpXa1VBmlOKaTU5Br5+VbqjL48p2/uMq085FfnJW7jMe6nA342Cly/22FhHuIeF3AHxxhll0FvTtFe0nGGLKbPWHnLO8FU3LHrWKMa85MSq/MPXHfLeMM2y57BTb/O7bjLomfgXQw4++zh2Cu6V2uG8+9cTWt43gopZSaadXhm7mN+3OfR5JCDyyZPJkcUL9+zbZXZsSq8mbUpWG9pOGZWpHQCSiKjGBFqtFJEYXgneH5a5LT7mt3xERWnxd6kRHXa4oVV5ZVtUHPa7H0r07Q7xH6EHwQZQgiUTIgSicgbzAXtUZWv/RKJHyFFjBQJSRBlNo03fiwiRBgSbhCCMJGsZ2MujALB7U0ueNJ6SrO+jR/4WUEg8teDP4h79h+CgDCW+LMhP6oPWHvLdCj5yNPVSZIk+9z/Rm1WTPMbYlhUQB+wxQsAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="linearity" title="linearity" src="/static/5619b5b0e5c0b3cb62d4f3230fa725dc/a331c/linearity.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>This is actually the same pattern I showed during the webinar on modelling and implementing distributed processes:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/uURh5ziGfb8?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>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.</p> <h2 id="when-this-approach-works-and-when-it-doesnt" style="position:relative;"><a href="#when-this-approach-works-and-when-it-doesnt" aria-label="when this approach works and when it doesnt permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>When This Approach Works (And When It Doesn’t)</h2> <p>This approach works when:</p> <p><strong>Your business logic can handle partial data</strong>: 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.</p> <p><strong>Eventual consistency is acceptable</strong>: 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.</p> <p><strong>Decisions can be corrected or refined</strong>: 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.</p> <p><strong>You have clear business rules for conflicts</strong>: When fraud assessment contradicts approval, fraud wins. When risk assessment arrives late, it updates data quality but doesn’t reverse committed decisions.</p> <p>This approach struggles when:</p> <p><strong>Perfect consistency is required</strong>: Financial accounting, legal compliance, and safety-critical systems often can’t tolerate any inconsistency, even briefly.</p> <p><strong>Business logic requires complete data</strong>: Some decisions genuinely can’t be made with partial information. Credit limit increases might require a complete financial analysis before any approval.</p> <p><strong>Rollback costs are high</strong>: If reversing a decision is expensive or impossible, you need stronger ordering guarantees before committing.</p> <p><strong>Users can’t tolerate uncertainty</strong>: Some interfaces need to show definitive status immediately, not “processing” or “partial data available.”</p> <h2 id="conclusion-embracing-the-chaos" style="position:relative;"><a href="#conclusion-embracing-the-chaos" aria-label="conclusion embracing the chaos permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Conclusion: Embracing the Chaos</h2> <p><strong>The world is chaotic, and we can’t stop the chaos, but we can stop fighting the chaos.</strong> 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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p><strong>If you’re dealing with such issues, I’m happy to help you through consulting or mentoring. <a href="mailto:[email protected]">Contact me</a> and we’ll find a way to unblock you!</strong></p> <p>Read also more in:</p> <ul> <li><a href="https://www.architecture-weekly.com/p/the-order-of-things-why-you-cant">The Order of Things: Why You Can’t Have Both Speed and Ordering in Distributed Systems</a>,</li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a>,</li> <li><a href="/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/">Dealing with Eventual Consistency and Idempotency in MongoDB projections</a></li> <li><a href="/en/saga_process_manager_distributed_transactions/">Saga and Process Manager - distributed processes in practice</a>,</li> <li><a href="https://www.architecture-weekly.com/p/predictable-identifiers-enabling">Predictable Identifiers: Enabling True Module Autonomy in Distributed Systems</a></li> <li><a href="https://www.architecture-weekly.com/p/dealing-with-eventual-consistency">Dealing with Eventual Consistency, and Causal Consistency using Predictable Identifiers</a>,</li> <li><a href="/en/event_driven_distributed_processes_by_example/">Event-driven distributed processes by example</a>,</li> <li><a href="https://www.architecture-weekly.com/p/workflow-engine-design-proposal-tell">Workflow Engine design proposal, tell me your thoughts</a>,</li> <li><a href="/en/how_to_have_fun_with_typescript_and_workflow/">How TypeScript can help in modelling business workflows</a>,</li> <li><a href="/en/how_to_update_past_data_in_event_sourcing/">Oops I did it again, or how to update past data in Event Sourcing</a>,</li> <li><a href="/en/event_transformations_and_loosely_coupling/">Event transformations, a tool to keep our processes loosely coupled</a>,</li> <li><a href="/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/">Testing asynchronous processes with a little help from .NET Channels</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to build MongoDB Event Store]]>https://event-driven.io/en/mongodb_event_store/https://event-driven.io/en/mongodb_event_store/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto" > <a class="gatsby-resp-image-link" href="/static/73ba4d0d67d570d0c57ecac006d1b9d6/b09c1/2025-01-10-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAAsTAAALEwEAmpwYAAADJUlEQVQ4yy2Ty28bZRTFvYcVrJEQFRILVCFVggWUPiREpS6QWKAICRb8NbBg0QUIJXFqQloeRV22iJAIN3UebpQ48SMeZzwzHo89Y3tmvm/etpPwQ3a6ODpXujpXRzr35FwZIuIxIjlHppcIsnNkcoYbjBnJBDfIEFGGFyT4QYwfxrgiZOAJhp4kjFOSyRQZJuQ6fRvt9Ajl8C/0epF2dQOttsnp8QZKZZ126xC1UcIyaliWysAXmHafju2gWz1Mx6FSr7NdLqNop+SkCLBr6/SWPqS7chtz6WPM5VuYK59iLN5m2CzSbjWp1k7oGSq+N8KVKX6Q4smEbApP/97k2+++5+D4iJz0XWz1AG35Mwb3PyDMv4m2+Alq/i76TzcYqbsoxoCt7TJ6p0sw6JOlIOOMIBoTpVN6zoij6glqxyAnvBHD9h724vvEhbf5b+VVpvnXGBfeYLr4CqK1wdGJwWaxRN+0ONT2edLaR7gx/aHPwJUo7Q6uiLGcPjlfRAzqT5Br1xB/3iH47TrhHzcJHt8lenSDkbZP12hjWjbS7rFrNljceUql0mL92Q7F7Rf8U9ym1tSwBja5eHyBED6uYzByTEZ2B1vdx2o+p6eUsNUydvNfhu1dhpZC5DqkA53Qt4mSjDCZkE7OSccX87ByQTzGDzNEPEWEGa4v6ZbytJduov/+NfrDBfRfv0J7sIDx6BtM9Ri1Vafb7dLXG8hwFk4812rdDjk/yHBFNE/Mc11cz8Pee4Bx7z2MwueY+TvI5StY+VuYvyxgagpbOwfsvKjQVlXcQR8vSJHR+NLhbPCDhLlT38OXkv7uGvq9qwQ/v0tSuAKrr+Pc/4h24QtGtkn5sMH23gGOY+MN+8h4QhhPMHrm7OBk7k4EGTJI8HyB8/xHzB+ukaxdJV19h/HqWwwK17EefoltKDSqFZpNBUs7QczbkxElZ5cOZ5/edRxM28a0HTq2zelJmdZxkVbtGe1GiVa1iFrbQqkW0dRjdGUXU69j9rrzpsww06kdnZwXRIgoQUSXHfWCGBFPXvb6bM4iPSNIL+Ysk8l870fZS90l5joZ8z+tRo51SVq0FgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2025 01 10 cover" title="2025 01 10 cover" src="/static/73ba4d0d67d570d0c57ecac006d1b9d6/b09c1/2025-01-10-cover.png" srcset="/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" sizes="(max-width: 626px) 100vw, 626px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I have always said that MongoDB is not the best choice for event storage, and guess what?</strong> I <a href="https://github.com/event-driven-io/emmett/releases/tag/0.23.0">just released</a> the stable version of the MongoDB event store in <a href="https://github.com/event-driven-io/emmett">Emmett</a>.</p> <p><strong>And let me explain today how to do it.</strong> It’ll be a detailed article, but I think it’s an interesting piece, so fasten your seat belts!</p> <p>A few things are needed for that. The first and most important thing is to have a motivated contributor!</p> <p><strong>I was lucky to have such with <a href="https://www.linkedin.com/in/alexander-lay-calvert-2179501b4/">Alexander Lay-Calvert</a>.</strong> 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.</p> <p>What else is needed? Let’s discuss that today!</p> <h2 id="event-stores-as-key-value-stores" style="position:relative;"><a href="#event-stores-as-key-value-stores" aria-label="event stores as key value stores permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Event stores as key-value stores</h2> <p><strong>In this article, we’ll use the canonical definition of event sourcing.</strong></p> <p><strong>Event Sourcing is about making decisions</strong>, 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.</p> <p><strong>Event Streaming is about moving information from one place to another</strong> and integrating multiple components. Read more about in <a href="/en/event_streaming_is_not_event_sourcing/">Event Streaming is not Event Sourcing!</a>.</p> <p>Event stores are not messaging tools. They may have similar capabilities as Event Streaming solutions, but the focus is different:</p> <ul> <li>event stores are focus on consistency, durability and quality of data,</li> <li>event streaming solutions (like Kafka) are focus on delivery, throughput and integration.</li> </ul> <p><strong><a href="/en/event_stores_are_key_value_stores">Event stores are key-value databases</a></strong> At least logically. In relational databases, records are called rows; in document databases, documents; in Event Sourcing, they’re called streams.</p> <p>In event stores, the stream is built from:</p> <ul> <li><strong>the key</strong> represents a record identifier (e.g. order id, invoice number, car license plate number, etc.),</li> <li><strong>the value</strong> represents a sequence of business events that happened for the specific record (e.g. <em>OrderInitiated</em>, <em>ShipmentScheduled</em>, <em>OrderCompleted</em>).</li> </ul> <p>Simple as that, still, most of the time, we were taught that event stores are append-only logs looking more or less like that:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d7aa587db7ee507d3ae382f30dbfb671/a331c/append_only_log.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACIElEQVQozzVPW08TYRDd32xiNDHoq0Wj8mKIkvgAYqFdSkvToBZaqoVYC22BIKBi2Vuv+93v3S5m25CczMyZnJwzY1GhMZMAM4A5FdpM727dYDNj148a5Uptr1JbW09XDurVg7rjD4gwiAjKFeUKEmkRJiGmANEQkpt/zq3rt9qn2/nidqGY3yl9qx/lcvmdYimXy7faZzddxwuGiHBCOCLSGiPlwwiIeEBjN9TemLuhCnncw1OoYi80I3Y3YnEfRwM86Y7kgEQ+nAxxlCQP/raH5ZR3+LFXWepV3zq1D/3Ka+f76mDvpVtf7VfeBLVlr7YS7C/5h2uj/WQZlF95B+9DSC3/8ufVxoPW1uKv7MJF9ll7K3VtPzneWrzOPj6xU5f2wmnm6Xk+GU7s578zD1u5xbP0o6viC4iYBZnxgAmg8oD2gQlC5YxVAKQb6h7QbqgCaHygfKADoFxg+lB3hzwAClNpQSIIFYRJwgSmglApdKRMrMxUmqnUE6WnUkdCTaSeUi5xopSIcIC4BTAP0QyYA8wxU8XS52rtsNW5OGmfN5qd49Z547jTaHZ+NNsh4hBzQATEAmBhhbMGMIdEICoRVZ82ssvvVuxcYbtQ+rJXLRR3s3a+tPt1PZ0ZQ4YTTQJIhMW4mPNZPgNYUGGYioSOhY6ZnEgdE26oMJipew0Hs2rVr8Z/ur7j9whXhKu5Jbq3T3zndPbqXIOZQsnl/D/dcT/lXGSP8AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="append only log" title="append only log" src="/static/d7aa587db7ee507d3ae382f30dbfb671/a331c/append_only_log.png" srcset="/static/d7aa587db7ee507d3ae382f30dbfb671/36ca5/append_only_log.png 200w, /static/d7aa587db7ee507d3ae382f30dbfb671/a3397/append_only_log.png 400w, /static/d7aa587db7ee507d3ae382f30dbfb671/a331c/append_only_log.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Indeed, physically, most of them are append-only logs.</strong> See: <a href="/en/emmett_postgresql_event_store/">Emmett PostgreSQL storage</a>, <a href="https://developers.eventstore.com/">EventStoreDB</a>, <a href="https://martendb.io/">Marten</a>, <a href="https://www.axoniq.io/products/axon-server">Axon Server</a>. But well, <a href="/en/relational_databases_are_event_stores/">the same can be said for the relational databases</a>. Internally, each operation (INSERT/UPDATE/DELETE) is appended to the <a href="https://www.architecture-weekly.com/p/the-write-ahead-log-a-foundation">Write-Ahead/Transaction Log</a>. Then, upon transaction commit, they’re applied to specific tables.</p> <p>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.</p> <p>Again, even if internally, our event stores are append-only logs, then still each event is logically mapped to specific streams:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e11b637444800378d0652b4d345c604c/a331c/append_only_log_to_stream.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACZUlEQVQozxXLSVPTYAAA0PxMPTB61ZsXZzyo6NFlBtphcUZGDgrCMGJZSkuhG2mbpGlDY9KmC02XpEuSb026hSUO9/cYOlkQdwaQB7C38O9TqfTfyFEylYrHExwv5vI8m8uzeW44sukjIwi7njel3hQRj8F0Rr0F8RYOJHgWXGu9VE7KcHIkno2lhcRV6eicPb3I9W3q+gGaP8DpA54Hrh9QP3jMWrNTq7dcP+iUYs1YSM9s3yTWW+nteiysxdaM1Lp2vqGerMjRzdpZWIlt1qKr1dhGLRpiMJ2oalWra3TqN3IHl99eVU9XxN13tbMQ//ONevS5cvBROf5S3ltWT1ek/WU58lX8/b5y+Km094FBCJXEYk1ViDtpZH4V1p/pMjucBN1Kkl190i3H++gOen5/TOH0ro9mxL838RTP77sjzBCMK1KprikE2WZTavHHxo1iDkyrq7b4yFj/Zxh9YI9M0zDqRSUabmR3pOOQLsY6epuBEEpSuV7XELAgdrE7H9mw3TMRnWBvhojb6XQtyzYG44Z4IWy9EHbe8t9f1hJb7U6fAY5TLvKqIgN74FhjRKft/H4u/LRTOgOIAAB6xhBi2jNHIxs4CDsQAYgJcbv9AeMAWC4JqiJDa+jYYwBxX2Xlk9VelXMQMVqynPnTKieV7GG7WrJt27FGADi22a5kDpkJJQWOl65lBKEDKaEemd5ObgM6mXuLwKic1348l3dfCxtL7dyeuwgcAAGd9apCfm2Jyahjtao1mw3X83RdZ1lWEASOKxSLgiiKlxeJTDqZTl6yV1me5wqFgmVZhLoAANsa/wdamSbCr5MokgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="append only log mapping to stream" title="append only log mapping to stream" src="/static/e11b637444800378d0652b4d345c604c/a331c/append_only_log_to_stream.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>So again, logically, our event stores are key-value databases:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/86aef7ca8de30f19457d2894324231b3/a331c/key_value_database.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACbElEQVQozxXLW0/aYACA4f7T3exyc/Fi2dSZebEYIzNxcVnEzMxtbsFtCIp4ABEYSIGy0tJSjkqhFHr4Dv16wCFOFpM3z91LOd7Ydv8i4kHLHU+mF+lMOBJNpjNn8YsCU87lS1m6eJlnhgZyvFtoeZY9ckdjxxtj54aybA8TB2HbhBaxHY7jE4mL8/NEKBQ+ikbPYvFwOHQYiXTkLrYIAMgECCBsYYyJSwFjIHdaiiJjqClys9m6sl3PHd3YjottD2ILIQthArHzsFgEERdgCyIMTY2CxqDdbvWUPgZap5Ll06GOkG0xp13xUhOTfYnuVxI9MSuzMUXM9PmEwsV6wm9VSA86NQqaw0I+J/AcNIethF/4+lQMLee3n5T3V4o7s0J4Jb8zywff5LafsYFXXGChtPuitPuS2XrcFbMUNgcsQ9erFWwo16lt4dcSf7BW/v78wcAcf7jGBRaEAx8fmOP3l4vf5rm9RXOomCYwBh0Kaj0mnxErZWiozfgH9vOMdLIhhJbFsy0+tCKcbgoHq/X4ZjWyKh1viNEN6fgd0FUIoK60KaCrRTpb4VlTH1wlP7L+R0qddqdT6++dfTcl43vyb0omU+tuSib32Blj9xZCgCAy+m3K0FSWKUgCD4ZyO70j/Fhsnft7MV8jvlU/8klnfunQV49v1o9W66fvDU0FwITAhAgZ6jXlwCFbzFZ5FkLYTH5iv8yIkbeN4HzjZJ3fm68dr9eCi82oTwq+Lv9c0lVZ14bGQwOtf0WlqnqjfS13u7bjYecGebc6HqVoLp7K0X/ES4an2WquVClwNehOiO0Q27aIgxBGmPwH0NYdC9XJ8ZwAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="key-value database" title="key-value database" src="/static/86aef7ca8de30f19457d2894324231b3/a331c/key_value_database.png" srcset="/static/86aef7ca8de30f19457d2894324231b3/36ca5/key_value_database.png 200w, /static/86aef7ca8de30f19457d2894324231b3/a3397/key_value_database.png 400w, /static/86aef7ca8de30f19457d2894324231b3/a331c/key_value_database.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>And that’s great, as it’s much easier to reason about the guarantees we should expect from our store after realising that.</p> <p>Plus, it allows us to implement event stores on top of <em>a bit more exotic</em> storage like MongoDB, DynamoDB, CosmosDB or Blob Storage.</p> <p>Ok, enough intro! Let’s discuss how to do it using the MongoDB example!</p> <h2 id="basic-event-stream-definition" style="position:relative;"><a href="#basic-event-stream-definition" aria-label="basic event stream definition permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Basic Event Stream Definition</h2> <p>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. <a href="/en/prototype_underestimated_design_skill/">Prototyping is an underestimated design skill</a>.</p> <p>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?</p> <p><strong>We said that the event store is a key-value database and that records are called streams.</strong> Let’s write that down then:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">Stream</span> <span class="token operator">=</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">StreamName</span> <span class="token operator">=</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">EventStore</span> <span class="token operator">=</span> Map<span class="token operator">&lt;</span>StreamName<span class="token punctuation">,</span> Stream<span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>It reflects our knowledge; the shape of <em>Stream</em> and type of <em>Stream Name</em> are unknown to us, but we already know that <em>Event Store</em> is a database, a map where <em>Stream Name</em> is a key and <em>Stream</em> is a value.</p> <p>Let’s now describe the Stream: it represents a sequence of business events that happened for a specific record.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Stream</span> <span class="token operator">=</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">StreamName</span> <span class="token operator">=</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">EventStore</span> <span class="token operator">=</span> Map<span class="token operator">&lt;</span>StreamName<span class="token punctuation">,</span> Stream<span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>Easy peasy, boom! We’re done!</p> <p>Ok, not quite. Let’s do a reality check with…</p> <h2 id="requirements-for-event-store" style="position:relative;"><a href="#requirements-for-event-store" aria-label="requirements for event store permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Requirements for event store</h2> <p>What are the requirements, then? In my opinion, at least:</p> <ul> <li><strong>Appending event at the end of the stream.</strong> We need to be able to record new business facts for a specific entity,</li> <li><strong>Reading all events from the stream.</strong> That’s a must to <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">get the current entity state from events</a>. We read all recorded events and apply them to get the current state.</li> <li><strong>The guarantee of the ordering within the stream.</strong> It’s important to know the order of events in our process. Both in getting the state and integrating it with other workflows.</li> <li><strong>Being able to read your writes.</strong> As event stores are databases, you should get the new events right after you append them.</li> <li><strong>Strong-consistent, atomic writes and <a href="/en/optimistic_concurrency_for_pessimistic_times/">optimistic concurrency</a>.</strong> 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.</li> <li><strong>Store both event data and metadata.</strong> Event data is obvious; it gathers all information related to the business fact that happened. Metadata is needed for the generic handling, such as <a href="/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/">telemetry data</a>.</li> </ul> <p>The 2nd tier of event stores features (so great to have but not must-haves):</p> <ul> <li><strong>Being able to subscribe to notifications about newly appended events (best if it’s push-based). Also, having <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">at-least once delivery guarantee</a>.</strong> That’s important for integrating different business workflows from different streams.</li> <li><strong>Built-in projections for building read models</strong>. We need to have performant queries. Reading all events from the stream is fine for business logic but not good enough for querying.</li> <li><strong>Global ordering of events.</strong> It’s both useful for building read models from multiple streams and integrating workflows.</li> <li><strong><a href="/en/gdpr_in_event_driven_architecture/#archive-data">Streams archiving.</a></strong> So, being able to move the obsolete events (e.g. from completed workflows) to some <em>cold storage</em>. The fact that we record all business information doesn’t mean that we need to keep them forever.</li> </ul> <p>We could expand our Event Store definition into:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">interface</span> <span class="token class-name">EventStore</span> <span class="token punctuation">{</span> <span class="token function">readStream</span><span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">)</span><span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token function">appendToStream</span><span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">,</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And show pseudo-code implementation as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token punctuation">{</span> <span class="token keyword">const</span> streams <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span>StreamName<span class="token punctuation">,</span> Stream<span class="token operator">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> appendToStream <span class="token operator">=</span> <span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">,</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> currentEvents <span class="token operator">=</span> streams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> streams<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>streamName<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token operator">...</span>currentEvents<span class="token punctuation">,</span> <span class="token operator">...</span>events<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> readStream <span class="token operator">=</span> <span class="token punctuation">(</span>streamName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> streams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> appendToStream<span class="token punctuation">,</span> readStream<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Now, let’s validate how that fits into the MongoDB capabilities.</p> <h2 id="mongodb-specifics" style="position:relative;"><a href="#mongodb-specifics" aria-label="mongodb specifics permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>MongoDB specifics</h2> <p><a href="/en/key-value-stores/">Document databases are key-value databases whose values ​​have a defined structure</a>. 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.</p> <p><strong>MongoDB is a document database. Documents are organised into collections, which are similar to tables in relational databases but without a fixed structure.</strong> 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.</p> <p><strong>As I wrote in <a href="/en/strategy_on_migrating_relational_data_to_document_based/">my other article</a>, contrary to common belief, document data is structured but just less rigidly.</strong> 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.</p> <p>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.</p> <h2 id="storing-event-as-a-document" style="position:relative;"><a href="#storing-event-as-a-document" aria-label="storing event as a document permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Storing event as a document</h2> <p><strong>The first idea could be to store events as documents in the events collection.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/97f1103ef4ac2a4f57b4871a687b164b/a331c/event-per-document.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOUlEQVQozyWRS2sTcRTF5yOIC5duXQmK4FfwC+jGlTtxK7gWdONCxEV13YpYVIitTa1taIxtQ141oTWJzaPONJNJMvN/3v9zZoJWJsLhcA/cA5f7c6RJQMXSpO1u/93q+1qjuV0o7per5Uq9Wm/1T739cnVj82ut0azUf3wrHbS7PZv+ESoGaR2hYioMqHjg+ls7xXa332ge5dY2dksHnze3S/uV/Jed3Hq+sFs6qDQKxb3Or4HQCQjLZewQrigYrhIhNYAQUiqljDEAAgAwoYRQxkAqa2xs4+xGApaBYsI4TFjQKWUcU4GZQIRGVCJMCZOES8o4YRJjHI0HkXeMZz4OfcooEzYrcxVTDuSspdvLur8m3VLcWZHununl9ElOnNVs76MIjjklQsc6OVcmYTjgHLiIHakTAEDdLff5ZXfp2vDt3Z+PL4xWb5+8vDF6ddX7cO/kyUU//8gPxXgSnrqjU288DiYkCkClDhOGUIJ6RXfp+vDNHf/Tg8GLK37+4eD1TW/51mT9/vDZJb/wtNX5Xa016oetvXKtcdgMA4+rxGEixmiGZz5gHxgSHCs6BgBBxpwhhiciGjI8BU51eq7iv2Z+LjhmBHOZOIvXGRQGKJpRHDEcUYIoDgmOMAoxCgnBlKDwrBO6R2jmR9MRiaagEi6so7XNuKmEUI4wI4ThhRCh/x3hxUA5isKMHOVMxgw0A+N876KBF4yCqbSptCnoTNKkKotzaebSpMLMhcmisvPFTsJlhuof+8pAzBriGzUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Event per document" title="Event per document" src="/static/97f1103ef4ac2a4f57b4871a687b164b/a331c/event-per-document.png" srcset="/static/97f1103ef4ac2a4f57b4871a687b164b/36ca5/event-per-document.png 200w, /static/97f1103ef4ac2a4f57b4871a687b164b/a3397/event-per-document.png 400w, /static/97f1103ef4ac2a4f57b4871a687b164b/a331c/event-per-document.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>That could work, as each event type will help a different data set. You should expect different information in <em>EmployeeOnboardingCompleted</em> and <em>RoomReservationRejected</em>. MongoDB’s flexible schema would benefit us.</p> <p>When appending events, we would insert a new document. We could nest data and metadata inside the document payload.</p> <p>How would we read the events from the specific stream? We could keep the reference for the stream name in the event metadata. <a href="https://www.mongodb.com/docs/manual/indexes/">MongoDB allows indexing documents</a>, so technically this could work.</p> <p>We could type our event as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> StreamName <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>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 <a href="https://docs.honeycomb.io/get-started/basics/observability/concepts/high-cardinality/">high cardinality of values</a>, which is not optimal for indexing.</p> <p>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? <a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.update/#std-label-update-query">MongoDB allow conditional checks</a> when storing new data, but only for a single document.</p> <p>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:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/gaoZdtQSOTo?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>Storing an event as a document will not give us proper guarantees and performance, so it’s a no-go.</strong> Document databases work best if we access them by document id. That’s how we can enforce consistency and concurrency and make it performant.</p> <p>What if we were…</p> <h2 id="storing-event-stream-as-a-document" style="position:relative;"><a href="#storing-event-stream-as-a-document" aria-label="storing event stream as a document permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Storing event stream as a document</h2> <p>Let’s look again at our type definition:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">StreamName</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> StreamName<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Stream</span> <span class="token operator">=</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre></div> <p>What if we just stored the Stream as a document containing events? We could also store additional metadata like:</p> <ul> <li>stream type (e.g. <em>Room Reservation</em>, <em>User Onboarding</em> etc.),</li> <li>stream position representing the number of events in the stream; this could also be used for the optimistic concurrency check,</li> <li>diagnostic information like when the stream was created, when it was last updated, etc.</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f04c597710690a2eb4cc9ae209a56d8d/a331c/stream-per-document.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACh0lEQVQozxXRy04TUQCA4fMGulWjweDKxJUbTIQNLDQmXhJXJoa40QVPoLiBSCIaDEgQtsQgVrQgtgoiBSmlLaX30k7bGTq9MGUuZ87MmcuZmd6UJ/jz5Qeaaas60Uwnk81/WnSFI7H1DZ8/ENoL7of2YwWG9QdCP7xr4UgsuB/1be+msxRxWli3VI0ArFuyaqi6TZeqP39vZrKFg1jSveLZ3PKvete2/UGPd9393fPHt7MbjGz4/IcUgw1bxaaiEQCRjjBRdAebDWzY2LBNq21aTWw0VN2RFF3GFsIG1gyDOJZlY92EKkGqqWACkGYhRZVrVJ36KzIhsXRQz/kkNsoXA9JREJYTJ5RPKGegcCKUM0KVggIH+ZqsaAgTIEMJIkWIfoyNnqPnetgvgwcj59mlJ6nJnsLM9bL7aej52erWO6TZEs8puoN0G8pIqrMKNoHMV/RGh4t+DY9cTU71VlyPU6MX6MXB+ORAfOIGszQUGr7Iro8bhkmcUw5xWlarg1UkSzwQ6yxudKrhz9Gxa7nZAcPd33kPZPf9+NQtavpm0fVsd7iL/jV+VOGS6UOmVEmks4lUjmFKEl8DIldS7E4tvBh/dSU/20dW+jvTQFu5F54YSE31sd+GAi8vlzfestV6PHGYpehkOpdIZYtFWuKPgcCxitHmIq70WDc910uWT8vG8m3+wyVp9qK8/BC+BtD3QlaJQZoaaRpWizQ7WIEyFIGmIoSQEF3Iv+kuzd81V++0Z4DmfdSeP9NY6DI9DzrTQA+NicgQj2kZKTJSJZEXOVbGBOzkYOGoQqeDfH5LYOMCmxAKfrGaE+k9kU2I5aRQ9IscgyRBrFLwOI+kugxPFGz+X/UPdTkRJEpJ48MAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Stream per document" title="Stream per document" src="/static/f04c597710690a2eb4cc9ae209a56d8d/a331c/stream-per-document.png" srcset="/static/f04c597710690a2eb4cc9ae209a56d8d/36ca5/stream-per-document.png 200w, /static/f04c597710690a2eb4cc9ae209a56d8d/a3397/stream-per-document.png 400w, /static/f04c597710690a2eb4cc9ae209a56d8d/a331c/stream-per-document.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Having that, we could redefine our stream type definition into:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">Stream</span> <span class="token operator">=</span> <span class="token punctuation">{</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> streamType<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> streamPosition<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> createdAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> updatedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token punctuation">{</span> <span class="token keyword">const</span> streams <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span>StreamName<span class="token punctuation">,</span> Stream<span class="token operator">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> appendToStream <span class="token operator">=</span> <span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">,</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1. Get the current stream or set the default</span> <span class="token keyword">const</span> stream <span class="token operator">=</span> streams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token punctuation">{</span> events<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> streamType<span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token operator">!</span><span class="token punctuation">,</span> <span class="token comment">// 🤔 how to get it?</span> createdAt<span class="token operator">:</span> now<span class="token punctuation">,</span> streamPosition<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> streamExists <span class="token operator">=</span> stream<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>streamPosition <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 2. Append events</span> stream<span class="token punctuation">.</span>events<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">...</span>events<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/// and update stream metadata</span> stream<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>streamPosition <span class="token operator">+=</span> events<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>streamExists<span class="token punctuation">)</span> stream<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>updatedAt <span class="token operator">=</span> now<span class="token punctuation">;</span> streams<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>streamName<span class="token punctuation">,</span> stream<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> readStream <span class="token operator">=</span> <span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">)</span><span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> streams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span><span class="token operator">?.</span>events <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> appendToStream<span class="token punctuation">,</span> readStream<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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?</p> <h2 id="stream-type" style="position:relative;"><a href="#stream-type" aria-label="stream type permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Stream Type</h2> <p>There are multiple ways to do it:</p> <ul> <li>we could add additional options parameters that’d contain the name,</li> <li>if we’re using languages like C# or Java, we could add the generic parameter with type and get its name,</li> <li>we can change the first parameter to be an object containing stream name and stream type,</li> <li>we can use the <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Name">URN-bases</a> format of Stream Name to include a stream type, e.g. “{streamType}:{streamId}“.</li> </ul> <p>All of them can be valid, but my personal preference is the last one. Why? Again, event stores are key-value databases. For them, <a href="https://www.architecture-weekly.com/p/using-s3-but-not-the-way-you-expected">the key to consistency is a proper key strategy</a>. 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.</p> <p>We could then redefine our stream name to:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">StreamType</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">StreamName<span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType <span class="token operator">=</span> StreamType<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">T</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token builtin">string</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre></div> <p>I use the nice TypeScript feature called <a href="https://www.typescriptlang.org/docs/handbook/2/template-literal-types.html">Literal Types</a>. 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.</p> <p>Now, we can add the function to parse our formatted stream name:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token generic-function"><span class="token function">fromStreamName</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType<span class="token operator">></span></span></span><span class="token punctuation">(</span> streamName<span class="token operator">:</span> StreamName<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> StreamNameParts<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> parts <span class="token operator">=</span> streamName<span class="token punctuation">.</span><span class="token function">split</span><span class="token punctuation">(</span><span class="token string">':'</span><span class="token punctuation">)</span> <span class="token keyword">as</span> <span class="token punctuation">[</span><span class="token constant">T</span><span class="token punctuation">,</span> <span class="token builtin">string</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> streamType<span class="token operator">:</span> parts<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> streamId<span class="token operator">:</span> parts<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">StreamNameParts<span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType <span class="token operator">=</span> StreamType<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> streamType<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">;</span> streamId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <h2 id="stream-id" style="position:relative;"><a href="#stream-id" aria-label="stream id permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Stream Id</h2> <p><strong>If our stream name is a string of a specific format, potentially allowing any string as stream ID, then how would we fit into <a href="https://www.mongodb.com/docs/manual/reference/bson-types/#std-label-objectid">MongoDB ObjectID</a>, which is used as document id?</strong></p> <p>From MongoDB docs:</p> <blockquote> <p>ObjectIds are small, likely unique, fast to generate, and ordered. ObjectId values are 12 bytes in length, consisting of:</p> <ul> <li>A 4-byte timestamp, representing the ObjectId’s creation, measured in seconds since the Unix epoch.</li> <li>A 5-byte random value generated once per process. This random value is unique to the machine and process.</li> <li>A 3-byte incrementing counter, initialised to a random value.</li> </ul> </blockquote> <p>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.</p> <p>Our adjusted type definition will look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">Stream<span class="token operator">&lt;</span> Type <span class="token keyword">extends</span> StreamType <span class="token operator">=</span> StreamType<span class="token punctuation">,</span> Name <span class="token keyword">extends</span> StreamName<span class="token operator">&lt;</span>StreamType<span class="token operator">></span> <span class="token operator">=</span> StreamName<span class="token operator">&lt;</span>StreamType<span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> Name<span class="token punctuation">;</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> streamId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> streamType<span class="token operator">:</span> Type<span class="token punctuation">;</span> streamPosition<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> createdAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> updatedAt<span class="token operator">?</span><span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>As you can see, this clearly shows that our stream has its type and the name derived from it.</p> <p>The updated dummy implementation will look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token punctuation">{</span> <span class="token keyword">const</span> streams <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span>StreamName<span class="token punctuation">,</span> Stream<span class="token operator">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> appendToStream <span class="token operator">=</span> <span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token class-name">StreamType</span> <span class="token operator">=</span> StreamType<span class="token operator">></span><span class="token punctuation">(</span> streamName<span class="token operator">:</span> StreamName<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> streamId<span class="token punctuation">,</span> streamType <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">fromStreamName</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1. Get the current stream or set the default</span> <span class="token keyword">const</span> stream <span class="token operator">=</span> streams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token punctuation">{</span> streamName<span class="token punctuation">,</span> events<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> streamId<span class="token punctuation">,</span> streamType<span class="token operator">:</span> streamType<span class="token punctuation">,</span> createdAt<span class="token operator">:</span> now<span class="token punctuation">,</span> streamPosition<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> streamExists <span class="token operator">=</span> stream<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>streamPosition <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token comment">// 2. Append events</span> stream<span class="token punctuation">.</span>events<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">...</span>events<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">/// and update stream metadata</span> stream<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>streamPosition <span class="token operator">+=</span> events<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>streamExists<span class="token punctuation">)</span> stream<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>updatedAt <span class="token operator">=</span> now<span class="token punctuation">;</span> streams<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>streamName<span class="token punctuation">,</span> stream<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> readStream <span class="token operator">=</span> <span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">)</span><span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> streams<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span><span class="token operator">?.</span>events <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> appendToStream<span class="token punctuation">,</span> readStream<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re almost there: finally implementing it in MongoDB! The final decision remains to be made.</p> <h2 id="streams-collection-layout" style="position:relative;"><a href="#streams-collection-layout" aria-label="streams collection layout permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Streams Collection layout</h2> <p>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.</p> <p>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.</p> <p><strong>Still, as we already know that streams have types, we could keep all the streams of the specific type in dedicated collections.</strong> That should help to optimise stream usage, potentially apply precise indexing strategies, and make other optimisations.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c207e5bd4a5264c58f4f87b218e93231/a331c/collection-per-stream-type.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACaElEQVQozxXQS08TQQDA8f0KnozRePPmyUQ8SIJRD168qAe/kJ5Rg1AwvDTB8kgAS1uCUO0DKm2wdNtC2+2+2t2d3ZnZmdnZV1tiDB/gf/j/BC8c8mDoR1fNi+7aWnIvnUmlM4VCqS62xGb7vNHuyvrm1s5JuXpcrpZOKn9Oz5gXhvEV45HAg9jGBCBXbHfWf6SPjk9zx5WV5MZmKrudSm1tb+zu7XxLJncz6ezhYWo/mysWdQAsBIkXCC4L+wDYmNhqbXD0zshPD/IfzNKMevDeuPildWtyV7Rtg/hDFsR+NObRGFFuONCEUGA8Bi7BGMJeVZ57CubvDxIT8tIr5dMEFPc6ilUTL5GlE8ZZMHJZaCOKCMeUmwgLzI8NhBzHgsq5nJiKFu9Gi3fwygM6c8OtJ8+aSrnyF9sDSj3Co0arUyiVT6s1AF2AXcEPxwAjTD0qF4PvE3zzSbAxGey+jNcfsk5W1fqKorrQ8uN/lI+6st6V1W5P1Q1gYSRQHlsQul5AXNcZSI6pAUXsi/uGmDFbOat5YHcK0OhQIDNk+EHAw5EfjRG53haoFxvOdU15TIMrykNkKsrqG2n+eXfhhZSYkhJTem662WqJYkPuiIRx6kWYeBbCAuHDvg0gYdQLqR+70HKxo359O1iYhMuPRku3lPlnRn62IVn5UkXXNUoQC0aIetfaYTgEGBEvZHxI/RGhDBmStvqafbk3Xr49XrqpzT02i3OS0i+XK6Yu0Wv2MSLewHGEwqVbb7fbsmQix4C2hYmmXTZ/L8jFWf3ks1L82CvMqM2fWu9MU+qGpQLXNaCjWQbA5D9+bCSSlCgkOQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Collection per stream type" title="Collection per stream type" src="/static/c207e5bd4a5264c58f4f87b218e93231/a331c/collection-per-stream-type.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>We could also allow custom collection resolution.</strong> For instance, if you’d like to separate storage per application module.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/397fd920ceece792a38aa2e7ea7e869e/a331c/database-per-module.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACaUlEQVQozwXBTU/TYAAA4N69eTAmJsaLiUfjyQRM/Cn6B4ycjAdvctMYZhBiAooxESMg40NgioPwMVo2ug/W0nbQvmv7fr9tabuOATKfR0qy86TTS7NLTbemvnzlQVytN2dm54sbW6tr60q5qpTVetNQa83llcLC0ooIk+7ZRZqdx2lPEqeJRykUomlZM/l5jxD1sPF9bu7n4mJ++dfyamFuIb8tl0rl/c3d3S1ZBghBLmgQICEkgDDiQrdOymp9V97f3lMqtUO9ZYdJF3EOMPIxk8tqpdbYV+sl5UA5qFaqDcwClxAJMhHGnSjpNjRT3ldLSkU7MimhcXqGROBRGp5mIkwah0eyUinJZcOyWRBHceYSLPmMEQRYcZitDJHCS7b2gjYWfGAwCCCjAMEgztLzfnrRT877nct+0ruKu//S7NLFWIIUMuQ400957gYevwfe3cbyZP2YAeeEc99jPIzCyN4NzYIwi4HxOwAq8VohQ5AyCTJGoU3zT6LpgfjH43j6IT+Y0jXNcwzMqYshwa6/MOSMD8LJAX/8QXsjp5snTstsu8eSzzjlAS1Pwb+v/c03uDiMKt+gUSKu5WIEoEdQ+3jmuf/+Tnfilpe7a298VLWTlnHkepYEMCYUO4W33of73sQja3SwXRjWHabrmts2AKGUUb8yi3dGxN6Iv5Xzm+vYtQTxfIolJqgIAvbnVTJ2vffpZjZ2LVl/Ztro2NQwBABhcZrF3X7UuQo6V1G3H6W96DRN0gvIuFSogpphNHemQGkcKJ/tnVG7tmS3qsDR2tiDjHmUeBQjzrDgkBHIKQqET6nl2v8Bb7MjiUVk34EAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Database per module" title="Database per module" src="/static/397fd920ceece792a38aa2e7ea7e869e/a331c/database-per-module.png" srcset="/static/397fd920ceece792a38aa2e7ea7e869e/36ca5/database-per-module.png 200w, /static/397fd920ceece792a38aa2e7ea7e869e/a3397/database-per-module.png 400w, /static/397fd920ceece792a38aa2e7ea7e869e/a331c/database-per-module.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Or separate storage for each customer?</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a3a5e9ead6b3534a1030fe756df544fa/a331c/database-per-tenant.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACdklEQVQozxXPT0/TUAAA8N69eTBoolc9EhMTEzhwUC/G78AnMJwkHrwYOJgQTQxkSAaIiQERmPwJC0NgZOxft3Zt2bqt69q+tq/v9b12jHarAzwYP8EvPyaMrsP+VS+6kevqyrfvXifgKtLPzcRxOnN4dMqWhQLLVeVWNs9mc+xpJu93wz+Dm350c9kbMJ3L0CHU8TxZbW1ub5sIcZK4nkhs7e7sJJM7+8n1xNZZsZAvlXPlcqZYNLGLPN8hxO8GDECIXASqbhY5Qao1csVSqSICiIPoGmDHQJD4Qaki8mJVOJdZXuTFmo097HUgoYxD/YugH/QHUk0pcUKxxMsNhVLai65t4kJK+9FfRDosJ5YrEssJpmVdBqHf7Vmuy0BKXQTIyRRNTrgHb+nBJD3ftQ3FQyZANsCu3zoj+xNe6o2bnKTH73Gbd0CLYMuhlHGIQ5FhrI57n+/g+Ufg0wNcWJJUagHNwYbpANrIGLPDndg9a/YhXHhiG02hbhDYRgT/l4mjk+3x7tpouDEWrj31+JW6XIdAsTE0XeLJSbL6rLcxFvwYCX69tFtcoya50Ph/htQjXoeUV5yTKXg6g9PTmFt1lDyxVc02TUx8qLrpKZiecdIfcGYGSvu4zSNo2oQwAGOXYCP10Y4N24uj6tyImZquG7TZqGta1UCQQrW9+ArFH2vxF3r8ebtyKLexWq8gFzHEJ17ngv5+F8ZuXy0PRbFb4dFrRUea0rAs1UDIhwqJDw8Wh6Kl+1H8Lqnu1ZoG1BuOi5mUAMRms5ZdMfLzgP2qZ+d0cU9XBRM0dAiAiy2raRa+AHZZyy2A4pKp5LUW39brNnH/ARgKE/5/tEtdAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Database per tenant" title="Database per tenant" src="/static/a3a5e9ead6b3534a1030fe756df544fa/a331c/database-per-tenant.png" srcset="/static/a3a5e9ead6b3534a1030fe756df544fa/36ca5/database-per-tenant.png 200w, /static/a3a5e9ead6b3534a1030fe756df544fa/a3397/database-per-tenant.png 400w, /static/a3a5e9ead6b3534a1030fe756df544fa/a331c/database-per-tenant.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Or maybe use a single database but combine the stream type with the customer name, or use another sharding strategy. Why not?</p> <p>Moreover, we can also allow users to select different Mongo databases for storage and logical separation.</p> <p><strong>I recommend using collection per stream type as the default choice</strong>, but two other approaches are also valid depending on the needs, so why not allow them all? Let’s do it!</p> <p>We’ll start by defining the options that users can provide to select our storage layout:</p> <p>Here are the single collection options:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">CollectionPerStreamTypeStorageOptions</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'COLLECTION_PER_STREAM_TYPE'</span><span class="token punctuation">;</span> databaseName<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Collection per stream type:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">SingleCollectionStorageOptions</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'SINGLE_COLLECTION'</span><span class="token punctuation">;</span> collectionName<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> databaseName<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>And a bit more advanced custom resolution</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">CustomStorageOptions</span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CUSTOM'</span><span class="token punctuation">;</span> collectionFor<span class="token operator">:</span> <span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token class-name">StreamType</span><span class="token operator">></span><span class="token punctuation">(</span>streamType<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">)</span> <span class="token operator">=></span> CollectionResolution<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">CollectionResolution</span> <span class="token operator">=</span> <span class="token punctuation">{</span> databaseName<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> collectionName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We allow the user to provide the function that will return the database name and collection name based on a defined algorithm.</p> <p>Let’s group it in the single union type marking that the need to provide one of those options:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">StorageOptions</span> <span class="token operator">=</span> <span class="token operator">|</span> SingleCollectionStorageOptions <span class="token operator">|</span> CollectionPerStreamTypeStorageOptions <span class="token operator">|</span> CustomStorageOptions<span class="token punctuation">;</span></code></pre></div> <p>The collection resolution could look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token generic-function"><span class="token function">resolveCollectionAndDatabase</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType<span class="token operator">></span></span></span><span class="token punctuation">(</span> streamType<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">,</span> options<span class="token operator">:</span> StorageOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> CollectionResolution <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>options<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'SINGLE_COLLECTION'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> collectionName<span class="token operator">:</span> options<span class="token punctuation">.</span>collectionName <span class="token operator">??</span> defaultCollectionName<span class="token punctuation">,</span> databaseName<span class="token operator">:</span> options<span class="token punctuation">.</span>databaseName<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'COLLECTION_PER_STREAM_TYPE'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> collectionName<span class="token operator">:</span> streamType<span class="token punctuation">,</span> databaseName<span class="token operator">:</span> options<span class="token punctuation">.</span>databaseName<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'CUSTOM'</span><span class="token operator">:</span> <span class="token keyword">return</span> options<span class="token punctuation">.</span><span class="token function">collectionFor</span><span class="token punctuation">(</span>streamType<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> defaultCollectionName <span class="token operator">=</span> <span class="token string">'streams'</span><span class="token punctuation">;</span></code></pre></div> <p>I think that’s straightforward, but to recap:</p> <ul> <li><strong>For single collection storage</strong>, we resolve the collection name based on the provided value or the default name.</li> <li><strong>For collection per stream type</strong>, we use the stream type name as the collection name.</li> <li><strong>For custom resolution</strong>, we take whatever the user’s creativity gives us.</li> </ul> <p>What would you say if we finally use MongoDB?</p> <h2 id="implementing-mongodb-event-store" style="position:relative;"><a href="#implementing-mongodb-event-store" aria-label="implementing mongodb event store permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Implementing MongoDB event store</h2> <p>Let’s define the function that will provide us with the real MongoDB connection. It can look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token generic-function"><span class="token function">collectionFor</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType<span class="token operator">></span></span></span><span class="token punctuation">(</span> streamType<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">,</span> client<span class="token operator">:</span> MongoClient<span class="token punctuation">,</span> options<span class="token operator">:</span> StorageOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> Collection<span class="token operator">&lt;</span>Stream<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> collectionName<span class="token punctuation">,</span> databaseName <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">resolveCollectionAndDatabase</span><span class="token punctuation">(</span> streamType<span class="token punctuation">,</span> options<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> db <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span>databaseName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> db<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>Stream<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">>></span></span></span><span class="token punctuation">(</span>collectionName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We use stream type and MongoDB client to resolve the database and return collection based on the storage resolution.</p> <p>Of course, we could add caching, automatic indexing on stream name, etc. <a href="https://github.com/event-driven-io/emmett">Emmett</a> does all of that, but let’s keep it out of this for brevity.</p> <p>With the collection resolution, we can update our event store implementation so that it is no longer dummy and use MongoDB.</p> <p>As we’ll be doing now a serious database operation, let’s change the Event Store definition into:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">interface</span> <span class="token class-name">EventStore</span> <span class="token punctuation">{</span> <span class="token function">readStream</span><span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">appendToStream</span><span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">,</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Yes, I just changed the result types to return Promise, making our operations asynchronous.</p> <p>Now, let’s define our event store setup. We’ll need to take MongoDB client and storage options.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">MongoDBEventStoreOptions</span> <span class="token operator">=</span> <span class="token punctuation">{</span> client<span class="token operator">:</span> MongoClient<span class="token punctuation">;</span> storage<span class="token operator">:</span> StorageOptions<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span>options<span class="token operator">:</span> MongoDBEventStoreOptions<span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> client<span class="token punctuation">,</span> storage <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token comment">// (...) the rest of the code. We'll get here</span> <span class="token punctuation">}</span></code></pre></div> <p>Here, we assume that the injected client is connected. Of course, that’s a simplified version; in <a href="https://github.com/event-driven-io/emmett">Emmett</a>, we support passing clients in any state, providing the connection string, and handling the client lifetime internally.</p> <h3 id="reading-stream-from-mongodb-document" style="position:relative;"><a href="#reading-stream-from-mongodb-document" aria-label="reading stream from mongodb document permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Reading stream from MongoDB document</h3> <p>Let’s see what our updated read stream will look like:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span>options<span class="token operator">:</span> MongoDBEventStoreOptions<span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> client<span class="token punctuation">,</span> storage <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">const</span> readStream <span class="token operator">=</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType <span class="token operator">=</span> StreamType<span class="token operator">></span></span></span><span class="token punctuation">(</span> streamName<span class="token operator">:</span> StreamName<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> streamType <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">fromStreamName</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Resolve collection</span> <span class="token keyword">const</span> collection <span class="token operator">=</span> <span class="token generic-function"><span class="token function">collectionFor</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>streamType<span class="token punctuation">,</span> client<span class="token punctuation">,</span> storage<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Read events from the stream document</span> <span class="token keyword">const</span> stream <span class="token operator">=</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span>findOne<span class="token operator">&lt;</span>WithId<span class="token operator">&lt;</span>Pick<span class="token operator">&lt;</span>Stream<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token string">'events'</span><span class="token operator">>>></span><span class="token punctuation">(</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> <span class="token punctuation">{</span> $eq<span class="token operator">:</span> streamName <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token comment">// just reading events</span> projection<span class="token operator">:</span> <span class="token punctuation">{</span> events<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> stream<span class="token operator">?.</span>events <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// (...) appendToStream</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> appendToStream<span class="token punctuation">,</span> readStream<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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 <a href="https://www.mongodb.com/docs/manual/reference/operator/projection/positional/">projection</a> that allows us to select only events without all stream metadata. It is a small but nice performance improvement.</p> <h3 id="appending-to-the-stream-in-the-mongodb-document" style="position:relative;"><a href="#appending-to-the-stream-in-the-mongodb-document" aria-label="appending to the stream in the mongodb document permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Appending to the stream in the MongoDB document</h3> <p>Let’s now implement our append method! It’ll look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span>options<span class="token operator">:</span> MongoDBEventStoreOptions<span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> client<span class="token punctuation">,</span> storage <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">const</span> appendToStream <span class="token operator">=</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType <span class="token operator">=</span> StreamType<span class="token operator">></span></span></span><span class="token punctuation">(</span> streamName<span class="token operator">:</span> StreamName<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> streamId<span class="token punctuation">,</span> streamType <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">fromStreamName</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Resolve collection</span> <span class="token keyword">const</span> collection <span class="token operator">=</span> <span class="token generic-function"><span class="token function">collectionFor</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>streamType<span class="token punctuation">,</span> client<span class="token punctuation">,</span> storage<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Append events upserting the document</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> <span class="token punctuation">{</span> $eq<span class="token operator">:</span> streamName <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token comment">// append events</span> $push<span class="token operator">:</span> <span class="token punctuation">{</span> events<span class="token operator">:</span> <span class="token punctuation">{</span> $each<span class="token operator">:</span> events <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// increment stream position</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'metadata.streamPosition'</span><span class="token operator">:</span> events<span class="token punctuation">.</span>length<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// set default metadata</span> $setOnInsert<span class="token operator">:</span> <span class="token punctuation">{</span> streamName<span class="token punctuation">,</span> <span class="token string-property property">'metadata.streamId'</span><span class="token operator">:</span> streamId<span class="token punctuation">,</span> <span class="token string-property property">'metadata.streamType'</span><span class="token operator">:</span> streamType<span class="token punctuation">,</span> <span class="token string-property property">'metadata.createdAt'</span><span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// update metadata</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'metadata.updatedAt'</span><span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// (...) readStream</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> appendToStream<span class="token punctuation">,</span> readStream<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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.</p> <p>We’re using <a href="https://www.mongodb.com/docs/manual/reference/method/db.collection.updateOne/">updateOne</a> 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.</p> <h3 id="optimistic-concurrency" style="position:relative;"><a href="#optimistic-concurrency" aria-label="optimistic concurrency permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Optimistic Concurrency</h3> <p>The final bit to provide the proper consistency guarantees is optimistic concurrency. You can read my <a href="/en/optimistic_concurrency_for_pessimistic_times/">general introduction</a> and <a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">technical implementation guidance</a>.</p> <p>We want to detect conflicting updates and race conditions. In other words, it handles concurrency correctly.</p> <p><strong>Optimistic concurrency is called optimistic, as we assume that conflict situations will be rare.</strong> 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.</p> <p>For detection of whether the conflicting change was made, we’ll use a stream version as it is incremented with each append.</p> <p>What does an optimistic concurrency implementation look like?</p> <ol> <li>Return the current stream position while reading events.</li> <li>Append the event, sending the current position as the expected one.</li> <li>Check if the position in the stream equals the expected position sent in the append operation.</li> <li>If they match, allow appending and set a new entity stream position.</li> <li>If not, throw or return an error.</li> </ol> <p>To be able to do it, we need to modify our event store definition:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">interface</span> <span class="token class-name">EventStore</span> <span class="token punctuation">{</span> <span class="token function">readStream</span><span class="token punctuation">(</span>streamName<span class="token operator">:</span> StreamName<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ReadStreamResult<span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">appendToStream</span><span class="token punctuation">(</span> streamName<span class="token operator">:</span> StreamName<span class="token punctuation">,</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> AppendToStreamOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>AppendToStreamResult<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">ReadStreamResult</span> <span class="token operator">=</span> <span class="token punctuation">{</span> currentStreamPosition<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">AppendToStreamOptions</span> <span class="token operator">=</span> <span class="token punctuation">{</span> expectedStreamPosition<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">AppendToStreamResult</span> <span class="token operator">=</span> <span class="token punctuation">{</span> nextStreamPosition<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>Let’s now apply that to our read stream method:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span>options<span class="token operator">:</span> MongoDBEventStoreOptions<span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> client<span class="token punctuation">,</span> storage <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">const</span> readStream <span class="token operator">=</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType <span class="token operator">=</span> StreamType<span class="token operator">></span></span></span><span class="token punctuation">(</span> streamName<span class="token operator">:</span> StreamName<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ReadStreamResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> streamType <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">fromStreamName</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Resolve collection</span> <span class="token keyword">const</span> collection <span class="token operator">=</span> <span class="token generic-function"><span class="token function">collectionFor</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>streamType<span class="token punctuation">,</span> client<span class="token punctuation">,</span> storage<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Read events from the stream document</span> <span class="token keyword">const</span> stream <span class="token operator">=</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span>findOne<span class="token operator">&lt;</span> WithId<span class="token operator">&lt;</span>Pick<span class="token operator">&lt;</span>Stream<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token string">'events'</span> <span class="token operator">|</span> <span class="token string">'metadata'</span><span class="token operator">>></span> <span class="token operator">></span><span class="token punctuation">(</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> <span class="token punctuation">{</span> $eq<span class="token operator">:</span> streamName <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token comment">// just reading events</span> projection<span class="token operator">:</span> <span class="token punctuation">{</span> events<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token string-property property">'metadata.streamPosition'</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> events<span class="token operator">:</span> stream<span class="token operator">?.</span>events <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> currentStreamPosition<span class="token operator">:</span> stream<span class="token operator">?.</span>metadata<span class="token punctuation">.</span>streamPosition <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// (...) appendToStream </span> <span class="token keyword">return</span> <span class="token punctuation">{</span> appendToStream<span class="token punctuation">,</span> readStream<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Not much has changed; we’re just reading the stream position and returning it (defaulting to 0).</p> <p>How would the append to stream method change?</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span>options<span class="token operator">:</span> MongoDBEventStoreOptions<span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> client<span class="token punctuation">,</span> storage <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">const</span> appendToStream <span class="token operator">=</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> StreamType <span class="token operator">=</span> StreamType<span class="token operator">></span></span></span><span class="token punctuation">(</span> streamName<span class="token operator">:</span> StreamName<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> AppendToStreamOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>AppendToStreamResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> streamId<span class="token punctuation">,</span> streamType <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token function">fromStreamName</span><span class="token punctuation">(</span>streamName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Resolve collection</span> <span class="token keyword">const</span> collection <span class="token operator">=</span> <span class="token generic-function"><span class="token function">collectionFor</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>streamType<span class="token punctuation">,</span> client<span class="token punctuation">,</span> storage<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> expectedStreamPosition<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>options<span class="token operator">?.</span>expectedStreamPosition<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// 1.a. Use provided expected stream position, or...</span> expectedStreamPosition <span class="token operator">=</span> options<span class="token punctuation">.</span>expectedStreamPosition<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> <span class="token comment">// 1.b. Get the current stream version</span> <span class="token keyword">const</span> currentStream <span class="token operator">=</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span>findOne<span class="token operator">&lt;</span> WithId<span class="token operator">&lt;</span>Pick<span class="token operator">&lt;</span>Stream<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token string">'metadata'</span><span class="token operator">>></span> <span class="token operator">></span><span class="token punctuation">(</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> <span class="token punctuation">{</span> $eq<span class="token operator">:</span> streamName <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token comment">// just reading the stream position</span> projection<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'metadata.streamPosition'</span><span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// and use it as the expected one</span> expectedStreamPosition <span class="token operator">=</span> currentStream<span class="token operator">?.</span>metadata<span class="token punctuation">.</span>streamPosition <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> nextStreamPosition <span class="token operator">=</span> expectedStreamPosition <span class="token operator">+</span> events<span class="token punctuation">.</span>length<span class="token punctuation">;</span> <span class="token comment">// 3. Append events upserting the document</span> <span class="token keyword">const</span> updatedStream <span class="token operator">=</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> <span class="token punctuation">{</span> $eq<span class="token operator">:</span> streamName <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string-property property">'metadata.streamPosition'</span><span class="token operator">:</span> expectedStreamPosition<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token comment">// append events</span> $push<span class="token operator">:</span> <span class="token punctuation">{</span> events<span class="token operator">:</span> <span class="token punctuation">{</span> $each<span class="token operator">:</span> events <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// set default metadata</span> $setOnInsert<span class="token operator">:</span> <span class="token punctuation">{</span> streamName<span class="token punctuation">,</span> <span class="token string-property property">'metadata.streamId'</span><span class="token operator">:</span> streamId<span class="token punctuation">,</span> <span class="token string-property property">'metadata.streamType'</span><span class="token operator">:</span> streamType<span class="token punctuation">,</span> <span class="token string-property property">'metadata.createdAt'</span><span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// update metadata</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'metadata.streamPosition'</span><span class="token operator">:</span> nextStreamPosition<span class="token punctuation">,</span> <span class="token string-property property">'metadata.updatedAt'</span><span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3. In case of expected version mismatch, throw an error</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>updatedStream<span class="token punctuation">.</span>upsertedCount <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Stream version doesn't match</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> nextStreamPosition <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> appendToStream<span class="token punctuation">,</span> readStream<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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.</p> <p>Once we have it, we pass it on to the update statement.</p> <p><strong>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.</strong> 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.</p> <p>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.</p> <p>Once we have it, we pass it on to the update statement.</p> <p><strong>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.</strong> 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.</p> <p><strong>Together with atomic updates, that gives strong guarantees to our MongoDB event store.</strong> Of course, we should consider adding retries in case of concurrency error, but you can read about it <a href="/en/idempotent_command_handling/#idempotency-and-optimistic-concurrency">in my other article</a>.</p> <h2 id="tradeoffs" style="position:relative;"><a href="#tradeoffs" aria-label="tradeoffs permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Tradeoffs</h2> <p>While we showed that it’s perfectly doable to build an event store on top of MongoDB, there are some caveats worth highlighting.</p> <p>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:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/gG6DGmYKk4I?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>Still, that becomes an issue for updating read models: no global ordering out of the box.</strong> 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 <a href="https://www.mongodb.com/docs/manual/core/replica-set-oplog/">oplog</a> or <a href="https://www.mongodb.com/docs/manual/changeStreams/">Change Streams</a>, but it’s still more complex (and less straightforward) than dedicated event stores that offer a built-in global offset.</p> <p><strong>In Emmett, we provided the option to have <a href="https://github.com/event-driven-io/emmett/blob/e8e0b3c8f9620dc42c4888f9dccbf4fd3e69d384/src/packages/emmett-mongodb/src/eventStore/projections/mongoDBInlineProjection.ts#L147"><em>inline</em> projections</a> stored in the stream document, together with events.</strong> That allows atomic updates in the same operation as events append. I’ll expand on it in the follow-up post.</p> <p><strong>There’s also a valid concern <a href="https://www.reddit.com/r/node/comments/1hy5n9t/comment/m6etazv">raised by Robert Kawecki</a>. The maximum size of the MongoDB document is 16MB.</strong> This is, actually, more than the raw JSON size, as BSON used in MongoDB is a binary format. If we <a href="https://www.kurrent.io/blog/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention">keep our streams short</a>, 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 <a href="https://www.eventstore.com/blog/snapshots-in-event-sourcing">snapshots</a>. I’ll cover this <em>2nd-day issue</em> in the dedicated blog article.</p> <p>Lastly, from a durability perspective, past <a href="https://jepsen.io/analyses/mongodb-4.2.6">Jepsen tests</a> 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.</p> <p>Otherwise, if per-stream concurrency and ordering suffice, this MongoDB-based strategy can still be a solid choice.</p> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>I’d still use <a href="/en/emmett_postgresql_event_store/">PostgreSQL event store</a> as the default choice, but MongoDB implementation appeared surprisingly good.</p> <p>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.</p> <p>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.</p> <p><strong>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.</strong></p> <p>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.</p> <p>If you’re in the Node.js space, don’t reinvent the wheel; just run:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> @event-driven-io/emmett-mongodb</code></pre></div> <p>And use it.</p> <p><strong>You can also check the fully working WebApi sample using it.</strong> You’ll find it <a href="https://github.com/event-driven-io/emmett/tree/main/samples/webApi/expressjs-with-mongodb">here</a>.</p> <p>If you’re not in Node.js land and want me to help you build it <a href="mailto:[email protected]">contact me</a>, I’ll try to help. You can have a look at <a href="https://github.com/event-driven-io/emmett/tree/main/src/packages/emmett-mongodb/src/eventStore">Emmet’s implementation</a>.</p> <p><strong>Please also share with me your thoughts, and share the article with your friends if you enjoyed it!</strong> That’d be much appreciated, as writing it took me a hellova time.</p> <p>If you liked this article, you may also find those interesting:</p> <ul> <li><a href="/en/event_stores_are_key_value_stores">Event stores are key-value databases!</a></li> <li><a href="/en/event_streaming_is_not_event_sourcing/">Event Streaming is not Event Sourcing!</a></li> <li><a href="/en/lets_build_event_store_in_one_hour/">Let’s build event store in one hour!</a></li> <li><a href="https://www.architecture-weekly.com/p/using-s3-but-not-the-way-you-expected">Using S3 but not the way you expected. S3 as strongly consistent event store</a></li> <li><a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Idempotent Command Handling]]>https://event-driven.io/en/idempotent_command_handling/https://event-driven.io/en/idempotent_command_handling/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2018e411f2f9c31e7df9adf38f8405b6/b09c1/2024-11-15-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADMElEQVQ4y13S20+bBRjH8ZcWSklPlLd9X3qgXelBSjmtQOkBLessshY65FTKISJiIWyuGwOcMBeIMrZMHMZT3ORim5PgdjHDpiQmbjNeLWpMvPHGG/+Rr3EuLnDx3D355PfL8wiCIFDrbWJsaJLJidOk0zli0QSpzBAn3l5nfuE9Ui92UC1a6Qi2Mzn4OtPjJxk5PkqzrxG3yYpZq+df59kIqIuKsetNhA55CFgsWLUajCVKnKIZn8OF22Kj1ddAiz+IZJQwaMtxSDbiwSiRulZMmgNgsaAk5vMTe8FLxOelPxSjq76BV1taSNQ2IBtM1NldiCUq1IKAUhAoFYrQqzVEG9uoNJj2g+XqMlqdDiYijVzMZdiYzbMxnWdldIz+UBh7RSU+k0S7x0ZHTTU1JpE6m4xZrUSvUmMX5f2gWzTSXevj6lCKzVwfDz9Z57c7N/jh6mVWs0MErBYidjNfzSa5u5Rl9/1xvswnOWo3UlakQNYb9oPNdhsr3Uf4/kKBRxuXeXD+LX76dJO7ZxfYXl6iLxpkbTDMd6tj3JhKs7cywkJnM/OdIXzlOkxa3X4wIEtcyiS4v5Tn54/W2D41zu87N/l7b5cLkxNUV4h8u9jDL1+c4M9vlnm0Oc07R+u5c7KLkEVEPHhlo6qM86/EuPVmhvvnpri3XODJrev8tbvNYDyOQaVhLRfn8fogv16fYWeuhwm/zKjXiNugR9IdqFyqKGY+0cbNfC/35sZ5cKbAH1ufsbVUQFuixmI0cdjp4drsy9x+LczXw2EWW5wcNqrxmCXMugMJvWIF7ybb2cx18XBtjiefX+Hxx+s0uVxPlxxmkYDNwZXJOLffiLLVFyRfY8avL8NRISFqtM/BIoUSSVuB16Al6XVxJvkSe6vLTB1LPVtSoFXJOKUQR/y1fJCNkaqS8OjUT//RUenAaXU/BxUKJYnkWZrqe5E0amayGq4tRuhNGOg+pqA9LNDfV0OhMEBPZykXT8vsnPPRJv2Xyl5pJxQ6vr9ydvhDTs39SG74Erl+F2NZmXQqxPBACdkBgUxaJjvgZyYvkRs5RFdYRZ1chUIoxl3lxyoH/gf/AbvPszEUehHxAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 11 15 cover" title="2024 11 15 cover" src="/static/2018e411f2f9c31e7df9adf38f8405b6/b09c1/2024-11-15-cover.png" srcset="/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" sizes="(max-width: 626px) 100vw, 626px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <blockquote> <p><em>“Is your command handling idempotent?”</em></p> </blockquote> <p>Sounds like a douchebag question to ask. Actually, that’s not a question but a statement that we want to sound smart.</p> <p>Maybe a definition from Wikipedia could help?</p> <blockquote> <p>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.</p> </blockquote> <p>Meh, even more douchy.</p> <p>Yet, if we frame it as:</p> <blockquote> <p>“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.</p> </blockquote> <p>Or:</p> <blockquote> <p>Should checking out a guest fail or succeed if the guest was already checked out?</p> </blockquote> <p>Now we’re talking! We’re bringing the discussion back. We can reason and discuss specific cases.</p> <p>Let’s say that we have the Guest Stay finances workflow explained in the <a href="/en/how_to_have_fun_with_typescript_and_workflow/">How TypeScript can help in modelling business workflows</a>. 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).</p> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">checkIn</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> CheckIn<span class="token punctuation">,</span> state<span class="token operator">:</span> GuestStayAccount<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> GuestCheckedIn <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestId<span class="token punctuation">,</span> roomId <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedIn'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Guest is already checked-in!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedOut'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Guest account is already checked out</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GuestCheckedIn'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestId<span class="token punctuation">,</span> roomId<span class="token punctuation">,</span> guestStayAccountId<span class="token operator">:</span> <span class="token function">toGuestStayAccountId</span><span class="token punctuation">(</span>guestId<span class="token punctuation">,</span> roomId<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> checkedInAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">NotExisting</span> <span class="token operator">=</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'NotExisting'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">CheckedIn</span> <span class="token operator">=</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'CheckedIn'</span><span class="token punctuation">;</span> balance<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">CheckedOut</span> <span class="token operator">=</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'CheckedOut'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">GuestStayAccount</span> <span class="token operator">=</span> NotExisting <span class="token operator">|</span> CheckedIn <span class="token operator">|</span> CheckedOut<span class="token punctuation">;</span> <span class="token keyword">const</span> initialState <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> GuestStayAccount <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'NotExisting'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>That could work for the basic scenario. <strong>Yet, even if we’re designing the business logic or backend code, we should always consider user experience.</strong> 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:</p> <blockquote> <p>No worries, we’ll return the Result instead of throwing an exception!</p> </blockquote> <p>That’s sweet, but for me, it’s Potato vs Potahto.</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/Mc3Fn7R9mkE?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>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.</p> <p><strong>Whether we throw exceptions or return <em>Result.Error</em>, it’ll be the same (bad) user experience.</strong> The flow is stopped with an error. It’s “just” syntactic sugar we prefer.</p> <p>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:</p> <ul> <li>the hotel clerk clicks check-in twice,</li> <li>someone uses the check-in booth,</li> <li>we have a quick check-in workflow, where automatically, a guest account is created, a confirmed reservation, and checked in,</li> <li>we’re integrating with external systems that can send us a web-hook multiple times,</li> <li>the process is asynchronous or initiated in other modules, and queues can retry command delivery.</li> </ul> <p>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?</p> <p>The easiest option is to just remove if statement:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedIn'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Guest is already checked-in!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Still, if we just do that, then business logic would go forward, and it’d return <em>GuestCheckedIn</em> 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?</p> <p><strong>We should have a way to say that we’re ignoring the command.</strong> There are multiple options to achieve it, for instance, by returning:</p> <ul> <li>null,</li> <li>result type informing that we’re ignoring processing,</li> <li>explicit event telling that error condition was achieved,</li> <li>empty array or events,</li> <li>another creative way you came up with.</li> </ul> <p>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. <strong>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.</strong></p> <p>An updated method will look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">checkIn</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestId<span class="token punctuation">,</span> roomId <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata <span class="token punctuation">}</span><span class="token operator">:</span> CheckIn<span class="token punctuation">,</span> state<span class="token operator">:</span> GuestStayAccount<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> GuestCheckedIn <span class="token operator">|</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedIn'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedOut'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Guest account is already checked out</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GuestCheckedIn'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestId<span class="token punctuation">,</span> roomId<span class="token punctuation">,</span> guestStayAccountId<span class="token operator">:</span> <span class="token function">toGuestStayAccountId</span><span class="token punctuation">(</span>guestId<span class="token punctuation">,</span> roomId<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> checkedInAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <h2 id="command-handling-infrastructure" style="position:relative;"><a href="#command-handling-infrastructure" aria-label="command handling infrastructure permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Command Handling infrastructure</h2> <p>Such change, of course, has to be reflected in our infrastructure. In Event Sourcing the flow looks as follow:</p> <ol> <li>Get the stream events and build the state from them.</li> <li>Run the business logic based on the state and passed command.</li> <li>Store new events in the same stream you’ve read.</li> </ol> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token comment">// 1. Aggregate the stream</span> <span class="token keyword">const</span> aggregationResult <span class="token operator">=</span> <span class="token keyword">await</span> eventStore<span class="token punctuation">.</span><span class="token function">aggregateStream</span><span class="token punctuation">(</span>streamName<span class="token punctuation">,</span> <span class="token punctuation">{</span> evolve<span class="token punctuation">,</span> initialState<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Use the aggregate state</span> <span class="token keyword">const</span> state <span class="token operator">=</span> aggregationResult<span class="token punctuation">.</span>state<span class="token punctuation">;</span> <span class="token keyword">const</span> currentStreamVersion <span class="token operator">=</span> aggregationResult<span class="token punctuation">.</span>currentStreamVersion<span class="token punctuation">;</span> <span class="token comment">// 2. Run business logic</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">handle</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newEvents <span class="token operator">=</span> <span class="token builtin">Array</span><span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span> <span class="token operator">?</span> result <span class="token operator">:</span> <span class="token punctuation">[</span>result<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token comment">// 3. Check if any changes have been made</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>newEvents<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> newEvents<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> newState<span class="token operator">:</span> state<span class="token punctuation">,</span> nextExpectedStreamVersion<span class="token operator">:</span> currentStreamVersion<span class="token punctuation">,</span> createdNewStream<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 4. Append result to the stream</span> <span class="token keyword">const</span> appendResult <span class="token operator">=</span> <span class="token keyword">await</span> eventStore<span class="token punctuation">.</span><span class="token function">appendToStream</span><span class="token punctuation">(</span> streamName<span class="token punctuation">,</span> newEvents<span class="token punctuation">,</span> <span class="token punctuation">{</span> expectedStreamVersion<span class="token operator">:</span> currentStreamVersion<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 5. Return result with updated state</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>appendResult<span class="token punctuation">,</span> newEvents<span class="token punctuation">,</span> newState<span class="token operator">:</span> newEvents<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span>evolve<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>If you’re not doing Event Sourcing then it’s the same stuff; it could look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token comment">// 1. Aggregate the stream</span> <span class="token keyword">const</span> findResult <span class="token operator">=</span> <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">findById</span><span class="token punctuation">(</span>recordId<span class="token punctuation">)</span>_<span class="token punctuation">;</span> <span class="token comment">// Use the aggregate state</span> <span class="token keyword">const</span> state <span class="token operator">=</span> findResult<span class="token punctuation">.</span>state <span class="token operator">??</span> <span class="token function">initialState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> currentStreamVersion <span class="token operator">=</span> findResult<span class="token punctuation">.</span>currentVersion<span class="token punctuation">;</span> <span class="token comment">// 2. Run business logic</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">handle</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3. Check if any changes have been made</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isIgnoredResult</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> newState<span class="token operator">:</span> state<span class="token punctuation">,</span> nextExpectedVersion<span class="token operator">:</span> currentVersion<span class="token punctuation">,</span> createdNewRecord<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 4. Append result to the stream</span> <span class="token keyword">const</span> storeResult <span class="token operator">=</span> <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">store</span><span class="token punctuation">(</span> recordId<span class="token punctuation">,</span> result<span class="token punctuation">,</span> <span class="token punctuation">{</span> expectedVersion<span class="token operator">:</span> currentVersion<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 5. Return result with updated state</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>storeResult<span class="token punctuation">,</span> newState<span class="token operator">:</span> result<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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 <a href="/en/optimistic_concurrency_for_pessimistic_times/">Optimistic Concurrency</a> 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 <a href="/en/optimistic_concurrency_for_pessimistic_times/">Optimistic Concurrency</a>. If they don’t, then run away; they’re not mature!</p> <h2 id="idempotency-and-optimistic-concurrency" style="position:relative;"><a href="#idempotency-and-optimistic-concurrency" aria-label="idempotency and optimistic concurrency permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Idempotency and Optimistic Concurrency</h2> <p>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?</p> <p><strong>The solution for that is the retry policy.</strong> We could wrap our command handling in the retry that would check if concurrency error was thrown.</p> <p>Let’s say that we have the following wrapper for the command handling:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> CommandHandler <span class="token operator">=</span> <span class="token operator">&lt;</span>State<span class="token punctuation">,</span> EventType <span class="token keyword">extends</span> <span class="token class-name">Event</span><span class="token operator">></span><span class="token punctuation">(</span> <span class="token function-variable function">evolve</span><span class="token operator">:</span> <span class="token punctuation">(</span>state<span class="token operator">:</span> State<span class="token punctuation">,</span> event<span class="token operator">:</span> StreamEvent<span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token function-variable function">initialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">async</span> <span class="token punctuation">(</span> eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">,</span> streamName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token function-variable function">handle</span><span class="token operator">:</span> <span class="token punctuation">(</span> state<span class="token operator">:</span> State<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> StreamEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>CommandHandlerResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...) Command handling code you saw above</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We can define it for our guest stay as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> handle <span class="token operator">=</span> <span class="token function">CommandHandler</span><span class="token punctuation">(</span><span class="token punctuation">{</span> evolve<span class="token punctuation">,</span> initialState <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And use it in our endpoint for checking in guest as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> guestStayAccountsApi <span class="token operator">=</span> <span class="token punctuation">(</span> eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WebApiSetup <span class="token operator">=></span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/guests/:guestId/stays/:roomId'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> CheckInRequest<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// 1. Prepare command </span> <span class="token keyword">const</span> guestStayAccountId <span class="token operator">=</span> <span class="token function">toGuestStayAccountId</span><span class="token punctuation">(</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>guestId<span class="token punctuation">,</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>roomId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> command<span class="token operator">:</span> CheckIn <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CheckIn'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestId<span class="token operator">:</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>guestId<span class="token punctuation">,</span> roomId<span class="token operator">:</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>roomId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> now<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 2. Run command handling logic</span> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">checkIn</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3. If no error was thrown, return success</span> <span class="token keyword">return</span> <span class="token function">Created</span><span class="token punctuation">(</span><span class="token punctuation">{</span> url<span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/guests/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>guestId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/stays/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>roomId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/periods/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">formatDateToUtcYYYYMMDD</span><span class="token punctuation">(</span>now<span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p><strong>Getting back to the retries.</strong> As our command handler is just a function, we could decorate it with retry code.</p> <p>Let’s start by defining the general retry policy. I’m using the <a href="https://www.npmjs.com/package/async-retry">async-retry</a> npm package. If you’re in C#, check <a href="https://www.pollydocs.org">Polly</a>, and in Java, check <a href="https://github.com/spring-projects/spring-retry">Spring Retry</a>. Each environment has a recommended package to handle that.</p> <p>The only thing I added is a bit easier way to pass function to check if we should retry on a specfic error.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> retry <span class="token keyword">from</span> <span class="token string">'async-retry'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">AsyncRetryOptions</span> <span class="token operator">=</span> retry<span class="token punctuation">.</span>Options <span class="token operator">&amp;</span> <span class="token punctuation">{</span> shouldRetryError<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>error<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> NoRetries<span class="token operator">:</span> AsyncRetryOptions <span class="token operator">=</span> <span class="token punctuation">{</span> retries<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> asyncRetry <span class="token operator">=</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> opts<span class="token operator">?</span><span class="token operator">:</span> AsyncRetryOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>opts <span class="token operator">===</span> <span class="token keyword">undefined</span> <span class="token operator">||</span> opts<span class="token punctuation">.</span>retries <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">retry</span><span class="token punctuation">(</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>bail<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>opts<span class="token operator">?.</span>shouldRetryError <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>opts<span class="token punctuation">.</span><span class="token function">shouldRetryError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">bail</span><span class="token punctuation">(</span>error <span class="token keyword">as</span> Error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">throw</span> error<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> opts <span class="token operator">??</span> NoRetries<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Let’s also define a check for concurrency error. It could look as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> isExpectedVersionConflictError <span class="token operator">=</span> <span class="token punctuation">(</span> error<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token operator">=></span> error <span class="token keyword">instanceof</span> <span class="token class-name">ExpectedVersionConflictError</span><span class="token punctuation">;</span></code></pre></div> <p>Depending on your storage engine this function will differ, for instance for PostgreSQL change it could be:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> isExpectedVersionConflictError <span class="token operator">=</span> <span class="token punctuation">(</span> error<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token operator">=></span> error <span class="token keyword">instanceof</span> <span class="token class-name">Error</span> <span class="token operator">&amp;&amp;</span> <span class="token string">'code'</span> <span class="token keyword">in</span> error <span class="token operator">&amp;&amp;</span> error<span class="token punctuation">.</span>code <span class="token operator">===</span> <span class="token string">'23505'</span><span class="token punctuation">;</span></code></pre></div> <p>Having that, we can adjust our command handler to:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> defaultRetryOptions<span class="token operator">:</span> AsyncRetryOptions <span class="token operator">=</span> <span class="token punctuation">{</span> retries<span class="token operator">:</span> <span class="token number">3</span><span class="token punctuation">,</span> minTimeout<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> factor<span class="token operator">:</span> <span class="token number">1.5</span><span class="token punctuation">,</span> shouldRetryError<span class="token operator">:</span> isExpectedVersionConflictError<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> CommandHandler <span class="token operator">=</span> <span class="token operator">&lt;</span>State<span class="token punctuation">,</span> EventType <span class="token keyword">extends</span> <span class="token class-name">Event</span><span class="token operator">></span><span class="token punctuation">(</span> <span class="token function-variable function">evolve</span><span class="token operator">:</span> <span class="token punctuation">(</span>state<span class="token operator">:</span> State<span class="token punctuation">,</span> event<span class="token operator">:</span> StreamEvent<span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token function-variable function">initialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">async</span> <span class="token punctuation">(</span> eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">,</span> streamName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token function-variable function">handle</span><span class="token operator">:</span> <span class="token punctuation">(</span> state<span class="token operator">:</span> State<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> StreamEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">{</span> retry<span class="token operator">:</span> AsyncRetryOptions <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>CommandHandlerResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token function">asyncRetry</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span><span class="token punctuation">{</span> <span class="token comment">// (...) Command handling code you saw before</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> options<span class="token operator">?.</span>retry <span class="token operator">??</span> defaultRetryOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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.</p> <h2 id="handling-duplicates" style="position:relative;"><a href="#handling-duplicates" aria-label="handling duplicates permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Handling duplicates</h2> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> randomUUID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:crypto'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> guestStayAccountId <span class="token operator">=</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> command<span class="token operator">:</span> CheckIn <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CheckIn'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestId<span class="token operator">:</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>guestId<span class="token punctuation">,</span> roomId<span class="token operator">:</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>roomId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> now<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">checkIn</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>But that’s not what we did, we did:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> guestStayAccountId <span class="token operator">=</span> <span class="token function">toGuestStayAccountId</span><span class="token punctuation">(</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>guestId<span class="token punctuation">,</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>roomId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>where <em>toGuestStayAccountId</em> creates a predictable id based on provided data</strong>:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> <span class="token function-variable function">toGuestStayAccountId</span> <span class="token operator">=</span> <span class="token punctuation">(</span> guestId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> roomId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">guest_stay_account-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>guestId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>roomId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre></div> <p>The stay can be represented by guest and room (it could also be reservation ID or whatever is predictable).</p> <p>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.</p> <h2 id="idempotency-handling-in-updates" style="position:relative;"><a href="#idempotency-handling-in-updates" aria-label="idempotency handling in updates permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Idempotency handling in updates</h2> <p>We can also apply the same pattern to the update operation, let’s say we have the following code for recording charge:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">recordCharge</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> chargeId<span class="token punctuation">,</span> amount <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata <span class="token punctuation">}</span><span class="token operator">:</span> RecordCharge<span class="token punctuation">,</span> state<span class="token operator">:</span> GuestStayAccount<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ChargeRecorded <span class="token punctuation">{</span> <span class="token function">assertIsCheckedIn</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ChargeRecorded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> chargeId<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> amount<span class="token operator">:</span> amount<span class="token punctuation">,</span> recordedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> assertIsCheckedIn <span class="token operator">=</span> <span class="token punctuation">(</span>state<span class="token operator">:</span> GuestStayAccount<span class="token punctuation">)</span><span class="token operator">:</span> state <span class="token keyword">is</span> CheckedIn <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'NotExisting'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Guest account doesn't exist!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedOut'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span>`Guest account <span class="token keyword">is</span> already checked out<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>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.</p> <p>Let’s change our state definition to include transaction ids:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">NotExisting</span> <span class="token operator">=</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'NotExisting'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">CheckedIn</span> <span class="token operator">=</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'CheckedIn'</span><span class="token punctuation">;</span> balance<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token comment">// new property</span> transactionIds<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">CheckedOut</span> <span class="token operator">=</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'CheckedOut'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">GuestStayAccount</span> <span class="token operator">=</span> NotExisting <span class="token operator">|</span> CheckedIn <span class="token operator">|</span> CheckedOut<span class="token punctuation">;</span> <span class="token keyword">const</span> initialState <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> GuestStayAccount <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'NotExisting'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We also need to change our <em>evolve</em> function, which we’re using to define <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">how we build our state from events</a>.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">evolve</span> <span class="token punctuation">(</span> state<span class="token operator">:</span> GuestStayAccount<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> GuestStayAccountEvent<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> GuestStayAccount <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckedIn'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'NotExisting'</span> <span class="token operator">?</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'CheckedIn'</span><span class="token punctuation">,</span> balance<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token operator">:</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ChargeRecorded'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedIn'</span> <span class="token operator">?</span> <span class="token punctuation">{</span> <span class="token operator">...</span>state<span class="token punctuation">,</span> balance<span class="token operator">:</span> state<span class="token punctuation">.</span>balance <span class="token operator">-</span> event<span class="token punctuation">.</span>amount<span class="token punctuation">,</span> <span class="token comment">// new line</span> transactionIds<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token operator">...</span>state<span class="token punctuation">.</span>transactionIds<span class="token punctuation">,</span> event<span class="token punctuation">.</span>chargeId <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'PaymentRecorded'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedIn'</span> <span class="token operator">?</span> <span class="token punctuation">{</span> <span class="token operator">...</span>state<span class="token punctuation">,</span> balance<span class="token operator">:</span> state<span class="token punctuation">.</span>balance <span class="token operator">+</span> event<span class="token punctuation">.</span>amount<span class="token punctuation">,</span> <span class="token comment">// new line</span> transactionIds<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token operator">...</span>state<span class="token punctuation">.</span>transactionIds<span class="token punctuation">,</span> event<span class="token punctuation">.</span>paymentId <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckedOut'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'CheckedIn'</span> <span class="token operator">?</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'CheckedOut'</span> <span class="token punctuation">}</span> <span class="token operator">:</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> _notExistingEventType<span class="token operator">:</span> <span class="token builtin">never</span> <span class="token operator">=</span> type<span class="token punctuation">;</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>For the classical approach, you’d need to extend your table schema.</p> <p><strong>And now, the question? How can we get the charge id?</strong> The sender should generate it. Who’s <em>“the sender”</em>? For instance:</p> <ul> <li>web page,</li> <li>mobile application,</li> <li>other module,</li> <li>external payment gateway.</li> </ul> <p>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.</p> <p>The updated logic handling idempotency correctly can look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">recordCharge</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> chargeId<span class="token punctuation">,</span> amount <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata <span class="token punctuation">}</span><span class="token operator">:</span> RecordCharge<span class="token punctuation">,</span> state<span class="token operator">:</span> GuestStayAccount<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ChargeRecorded <span class="token operator">|</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token function">assertIsCheckedIn</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// idempotence check</span> <span class="token keyword">if</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>transactionIds<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>chargeId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ChargeRecorded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> chargeId<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> amount<span class="token operator">:</span> amount<span class="token punctuation">,</span> recordedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <h2 id="look-ma-no-exceptions" style="position:relative;"><a href="#look-ma-no-exceptions" aria-label="look ma no exceptions permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Look Ma’ No Exceptions!</h2> <p>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?</p> <p>It depends, I see a few ways:</p> <ul> <li>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.</li> <li>The same for events that are part of the <a href="/en/saga_process_manager_distributed_transactions/">asynchronous workflow</a>.</li> <li>if they represent idempotent handling, then return an empty array or explicitly ignore the object.</li> <li>if they’re important to business, I’d model them as events and store them in the stream.</li> </ul> <p>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.</p> <p>Having that we could define additional event type:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">ChargeFailed</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ChargeFailed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> chargeId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> guestStayAccountId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token string">'NotCheckedIn'</span> <span class="token operator">|</span> <span class="token string">'AlreadyCheckedOut'</span><span class="token punctuation">;</span> failedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">PaymentFailed</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'PaymentFailed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> paymentId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> guestStayAccountId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token string">'NotCheckedIn'</span> <span class="token operator">|</span> <span class="token string">'AlreadyCheckedOut'</span><span class="token punctuation">;</span> failedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token comment">// (...) other event types</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GuestStayAccountEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> GuestCheckedIn <span class="token operator">|</span> ChargeRecorded <span class="token operator">|</span> ChargeFailed <span class="token comment">// &lt;= new</span> <span class="token operator">|</span> PaymentRecorded <span class="token operator">|</span> PaymentFailed <span class="token comment">// &lt;= new</span> <span class="token operator">|</span> GuestCheckedOut <span class="token operator">|</span> GuestCheckoutFailed<span class="token punctuation">;</span></code></pre></div> <p>Instead of throwing exception, we could return now an event:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">function</span> <span class="token function">recordCharge</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> chargeId<span class="token punctuation">,</span> amount <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata <span class="token punctuation">}</span><span class="token operator">:</span> RecordCharge<span class="token punctuation">,</span> state<span class="token operator">:</span> GuestStayAccount<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ChargeRecorded <span class="token operator">|</span> ChargeFailed <span class="token operator">|</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token comment">// failure</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'CheckedIn'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ChargeFailed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> chargeId<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> reason<span class="token operator">:</span> state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'NotExisting'</span><span class="token operator">?</span> <span class="token string-property property">'NotCheckedIn'</span> <span class="token operator">:</span> <span class="token string">'AlreadyCheckedOut'</span> failedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">if</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>transactionIds<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>chargeId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ChargeRecorded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> chargeId<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> amount<span class="token operator">:</span> amount<span class="token punctuation">,</span> recordedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>We can orchestrate it like this:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span> eventStore<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token function">recordCharge</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// check if it's failed event</span> <span class="token keyword">if</span><span class="token punctuation">(</span>result <span class="token operator">!==</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">&amp;&amp;</span> result<span class="token punctuation">.</span>type <span class="token operator">===</span> <span class="token string">'ChargeFailed'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// let's ignore it, we could also throw error here</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h2 id="generic-idempotency-handling" style="position:relative;"><a href="#generic-idempotency-handling" aria-label="generic idempotency handling permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Generic idempotency handling</h2> <p>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.</p> <p><strong>We could store the handled command ids in the external store and run only business logic if it wasn’t already handled.</strong> The store could be defined as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">IdempotencyKeyStore</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token function-variable function">tryLock</span><span class="token operator">:</span> <span class="token punctuation">(</span> key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">{</span> timeoutInMs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">boolean</span> <span class="token operator">|</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token builtin">boolean</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function-variable function">accept</span><span class="token operator">:</span> <span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">void</span> <span class="token operator">|</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function-variable function">release</span><span class="token operator">:</span> <span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">void</span> <span class="token operator">|</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>The store has the following methods:</p> <ul> <li><em>tryLock</em> - that tries to lock the specified key for a certain period of time. Returns true if the lock was acquired and false otherwise.</li> <li><em>accept</em> - accepts the key lock after successful handling.</li> <li><em>release</em> - releases the lock in case of handling failure.</li> </ul> <p>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.</p> <p>Having that, we can define our wrapper as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">IdempotentOptions<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> store<span class="token operator">:</span> IdempotencyKeyStore<span class="token punctuation">;</span> key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token function-variable function">defaultResult</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token constant">T</span> <span class="token operator">|</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">;</span> timeoutInMs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">idempotent</span> <span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token function-variable function">fn</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> options<span class="token operator">:</span> IdempotentOptions<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> key<span class="token punctuation">,</span> timeoutInMs<span class="token punctuation">,</span> defaultResult<span class="token punctuation">,</span> store <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token comment">//Attempt to acquire the lock for the specified key</span> <span class="token keyword">const</span> appended <span class="token operator">=</span> <span class="token keyword">await</span> store<span class="token punctuation">.</span><span class="token function">tryLock</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token punctuation">{</span> timeoutInMs <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// If it was used, return the default result</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>appended<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">defaultResult</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment">// run handler</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">fn</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Mark operation as successful</span> <span class="token keyword">await</span> store<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Release key if it was used already</span> <span class="token keyword">await</span> store<span class="token punctuation">.</span><span class="token function">release</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span> error<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’re passing the function to wrap together with options:</p> <ul> <li>idempotency store,</li> <li>idempotency key,</li> <li>default result if the command was already handled,</li> <li>optional lock timeout.</li> </ul> <p>Having that, we:</p> <ol> <li>Attempt to acquire the lock for the specified key.</li> <li>If the attempt wasn’t successful return the default result.</li> <li>If we acquired lock, then run the business logic.</li> <li>If it fails, release the lock. We don’t want to lock the key, let someone fix the state condition, and retry later.</li> <li>If it was successful, accept the lock and return the result.</li> </ol> <p>The example lock store based on Redis could look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> defaultTryLockPeriod <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token comment">// 1 second</span> <span class="token keyword">const</span> defaultLockPeriod <span class="token operator">=</span> <span class="token number">86400</span><span class="token punctuation">;</span> <span class="token comment">// 1 day</span> <span class="token keyword">const</span> redisStore<span class="token operator">:</span> IdempotencyKeyStore <span class="token operator">=</span> <span class="token punctuation">{</span> tryLock<span class="token operator">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">(</span> key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">{</span> timeoutInMs<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> redis<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token string">"in-progress"</span><span class="token punctuation">,</span> <span class="token string">"NX"</span><span class="token punctuation">,</span> <span class="token string">"EX"</span><span class="token punctuation">,</span> options<span class="token operator">?.</span>timeoutInMs <span class="token operator">??</span> defaultTryLockPeriod<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> result <span class="token operator">===</span> <span class="token string">"OK"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">accept</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Update the key to "completed" status with a longer TTL</span> <span class="token keyword">await</span> redis<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token string">"completed"</span><span class="token punctuation">,</span> <span class="token string">"XX"</span><span class="token punctuation">,</span> <span class="token string">"EX"</span><span class="token punctuation">,</span> defaultLockPeriod<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1 day TTL</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">release</span><span class="token operator">:</span> <span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> redis<span class="token punctuation">.</span><span class="token function">del</span><span class="token punctuation">(</span>key<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Having that, we could decorate our command handler with idempotent handling:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> CommandHandler <span class="token operator">=</span> <span class="token operator">&lt;</span>State<span class="token punctuation">,</span> EventType <span class="token keyword">extends</span> <span class="token class-name">Event</span><span class="token operator">></span><span class="token punctuation">(</span> <span class="token function-variable function">evolve</span><span class="token operator">:</span> <span class="token punctuation">(</span>state<span class="token operator">:</span> State<span class="token punctuation">,</span> event<span class="token operator">:</span> StreamEvent<span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token function-variable function">initialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">async</span> <span class="token punctuation">(</span> idempotencyKeyStore<span class="token operator">:</span> IdempotencyKeyStore<span class="token punctuation">,</span> eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">,</span> streamName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token function-variable function">handle</span><span class="token operator">:</span> <span class="token punctuation">(</span> state<span class="token operator">:</span> State<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> StreamEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> options<span class="token operator">:</span> <span class="token punctuation">{</span> retry<span class="token operator">:</span> AsyncRetryOptions<span class="token punctuation">,</span> idempotencyKey<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>CommandHandlerResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token function">idempotent</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">asyncRetry</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span><span class="token punctuation">{</span> <span class="token comment">// (...) Command handling code you saw before</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> options<span class="token operator">?.</span>retry <span class="token operator">??</span> defaultRetryOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> idempotencyKeyStore<span class="token operator">:</span> IdempotencyKeyStore<span class="token punctuation">;</span> key<span class="token operator">:</span> options<span class="token punctuation">.</span>IdempotencyKey<span class="token punctuation">,</span> <span class="token function-variable function">defaultResult</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> state<span class="token punctuation">,</span> currentStreamVersion <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> eventStore<span class="token punctuation">.</span><span class="token function">aggregateStream</span><span class="token punctuation">(</span>streamName<span class="token punctuation">,</span> <span class="token punctuation">{</span> evolve<span class="token punctuation">,</span> initialState<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> newEvents<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> newState<span class="token operator">:</span> state<span class="token punctuation">,</span> nextExpectedStreamVersion<span class="token operator">:</span> currentStreamVersion<span class="token punctuation">,</span> createdNewStream<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Nothing fancy besides getting the current state in case we already handled this command.</p> <p>The endpoint will look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">const</span> guestStayAccountsApi <span class="token operator">=</span> <span class="token punctuation">(</span> eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">,</span> idempotencyKeyStore<span class="token operator">:</span> IdempotencyKeyStore<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WebApiSetup <span class="token operator">=></span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/guests/:guestId/stays/:roomId/periods/:checkInDate/charges'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> RecordChargeRequest<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> guestStayAccountId <span class="token operator">=</span> <span class="token function">parseGuestStayAccountId</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> command<span class="token operator">:</span> RecordCharge <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'RecordCharge'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> chargeId<span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>chargeId<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> amount<span class="token operator">:</span> <span class="token function">Number</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>amount<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> now<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span> eventStore<span class="token punctuation">,</span> idempotencyKeyStore<span class="token punctuation">,</span> guestStayAccountId<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">recordCharge</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> idempotencyKey<span class="token operator">:</span> command<span class="token punctuation">.</span>chargeId <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">NoContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And guess what? That’s also a way how external API providers are handling it:</p> <ul> <li><a href="https://stripe.com/docs/api/idempotent_requests">Stripe</a></li> <li><a href="https://developer.visa.com/capabilities/vpp/docs-how-to">Visa</a></li> <li><a href="https://www.checkout.com/docs/payments/manage-payments/retry-a-payment">Checkout</a></li> </ul> <p>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 <a href="https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/">Idempotency-Key header</a>. The choice is yours, but the logic will be the same.</p> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p>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.</p> <p>So, how are you making your command handling idempotent? If you haven’t thought about it yet, maybe now’s the time.</p> <p><strong>If you need to help in applying those practices, I’m here to help.</strong> Check my <a href="/en/training/">training</a> page. A workshop is the most effective way to jump-start, I’m also doing consulting and mentoring, <a href="mailto:[email protected]">drop me an e-mail!</a> and we’ll find the best way to help you and your team.</p> <p><strong>And guess what, all of those you saw in the article, is already available in <a href="https://github.com/event-driven-io/emmett/">Emmett</a>!</strong> Come join us, we have cookies! <a href="https://discord.gg/fTpqUTMmVa">Join our Discord channel</a> and discuss further questions!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Bootstrapping CRUD with Pongo]]>https://event-driven.io/en/crud_with_pongo/https://event-driven.io/en/crud_with_pongo/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto" > <a class="gatsby-resp-image-link" href="/static/460a24d0a3770557e48cb10508656e1b/b09c1/2024-10-27-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4iAAAuIgGq4t2SAAADJElEQVQozwEZA+b8AH7F3o/FypPGxJbIxZzLxpnLy5bLmbHZgqXOhKfTmKnSjKvQeqvPeqrQf6zPfafSeMG7kPCdsOuap+mXogB1x+SUztze14302Wvy23f43Wzix1WelmDKyLD28/D99un26L774o3/4GL/3mb/4F/8t1f7k2L6mGD4lV8AaMTqgc3y2dWL+NZc8tVi6tN8en1wWF5l6urp9vXy2Nnb6efrwcHBvbWE+ddh/N1Y+bFO+4pW+I1V9YxXAFrA6XjI7tPPgfrUUfXTWX15X2lwek5RUKmjn9DNx2xtbX+AgcnIx255hsarUv/hT/esRvuGUPiIT/SGTwBJt99gud7Wy3D60U3711jIsmOBgYV7d3HMyMTW1tK7u7uSkpPLyc58eGbux0n/20v3qj76gkv3hEn0gkkAQbbgQ6vU2Mhh+89F99BS/9lKx6xOxLaR4N/c19LM7u7x7eLG3seF7M1j/9dN/NZF96c3+YFM9oRI8oBGADew30Cm0NTHWPrPM/bQQ/jVZ9C4XZWSgLKppk9IR8PAwvHnyfbcif/ZXv3MO/7TNfWmK/JxNO5xMe1tNAAxr91Lj7nKc1nycELqnnLQvKvIraWbmpaPhoN5V1CVhn/MuK7azcnXvqX5kGv7knDvdV/rZGLsW2HmZGgArlVw4zFB+DNA9jZDiFRrSH+TSXuSUbDUGnyfG198GWWDQnuTZ2FltoGS66zN6qnK4J/B6KLF6KTF5arEAP0ZKqgHE6ASHO8UI2FZeB2m1CVJXEaVtCq87guCrEllciKTu0uHrOqFoqQtR5ROXuOTsbBlflsgMLZohgDyITKACxJQDhHfGCRpUGsnqNQKOEwvfJlAx/UmjK8bQlAokrdqoMTUe5R0LDyQVWTsmrmpZHxmM0m8eJUA7Cc06h8p4yIs5xgiXUJZNb7sMbzwNLblM63XNLfmLbnqMsHxYpK03oyl3JSy35Wz1Yqm14Kk342w14uqAN0yPdAoLcYlK+MnKV06Siex5DuZtDGiySuu3jOw3DOYuyOs2V2Vu9mDnq5pfr91kNGHqM2EoqhmfcB5lYrB1zow+CpDAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 10 27 cover" title="2024 10 27 cover" src="/static/460a24d0a3770557e48cb10508656e1b/b09c1/2024-10-27-cover.png" srcset="/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" sizes="(max-width: 626px) 100vw, 626px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>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.</strong> 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.</p> <p>Still, sometimes you don’t need all of that.</p> <p><strong>Sometimes you just need a <em>bag for data</em>, or the <em>CRUD</em> as most of us prefer to call it.</strong></p> <p><strong>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.</strong> 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.</p> <p>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.</p> <p><strong>CRUD can also be a valid approach for proof of concepts.</strong> 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.</p> <p>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.</p> <p>We need also to remember that we should not set this decision in stone.</p> <p><strong>That’s why I like to match CRUD with <a href="https://event-driven.io/en/cqrs_facts_and_myths_explained/">CQRS</a>.</strong> How come? Aren’t they contradicting? Not in my world!</p> <p><strong>CQRS stands for Command Query Responsibility Segregation.</strong> It’s a structural pattern. It tells us to slice our business application by the behaviour and then segregate them into two responsibilities:</p> <ul> <li>Command handling - business logic that can change state but should not be returning business data,</li> <li>Query handling - returns data but does not change the state (_“Asking a question should not change the answer”).</li> </ul> <p>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!</p> <p>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!</p> <p><strong>Why Pongo?</strong></p> <ul> <li>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,</li> <li>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 <a href="https://neon.tech">Neon</a>, <a href="https://supabase.com/">Supabase</a>, <a href="https://vercel.com">Vercel</a>, etc.</li> <li>MongoDB-like API is easy to learn and is well-known to many of us.</li> <li>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,</li> <li>Pongo will get you covered with built-in migrations, so there is no need to care a lot about the database schema.</li> </ul> <p>All of that makes a good combination for fast bootstrapping.</p> <p>For basics, you can check previous articles:</p> <ul> <li><a href="https://event-driven.io/en/introducting_pongo">Pongo - Mongo but on Postgres and with strong consistency benefits</a></li> <li><a href="https://event-driven.io/en/pongo_behind_the_scenes/">Pongo behind the scenes</a></li> <li><a href="https://event-driven.io/en/pongo_strongly_typed_client/">Pongo gets strongly-typed client, migrations, and command line tooling</a></li> <li><a href="https://event-driven.io/en/sql_support_in_pongo/">Running a regular SQL on Pongo documents</a></li> </ul> <p>And <a href="https://event-driven-io.github.io/Pongo/getting-started.html">documentation</a>.</p> <p>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.</p> <p><strong>The typical flow in CRUD for updating records looks as follows:</strong></p> <ol> <li>Validate the incoming request.</li> <li>Read the current state.</li> <li>Do necessary validation based on it.</li> <li>Run business logic.</li> <li>Update the state.</li> <li>Store it.</li> </ol> <p><strong>Similarly, for deletion:</strong></p> <ol> <li>Validate the incoming request.</li> <li>You read the current state.</li> <li>Do the necessary validation, checking if you can delete it.</li> <li>Delete it.</li> </ol> <p><strong>For creation, it’s even simpler; you just:</strong></p> <ol> <li>Validate the incoming request.</li> <li>Generate the new state based on request data.</li> <li>Store it.</li> </ol> <p><strong>If we’d like to be sneaky, we could wrap it in a single flow that covers all of those cases.</strong></p> <ol> <li>Validate the incoming request.</li> <li>Read the current state.</li> <li>If a state exists and you want to update it, do the necessary validation based on it.</li> <li>Run business logic. Returning new state, updated state, or null if you want to delete the state.</li> <li>Depending on the result of the business logic:</li> </ol> <ul> <li>Create if state didn’t exist and result state is not null,</li> <li>Update if state existed and result state is different and not null,</li> <li>Delete if state existed and result state is null,</li> <li>Do nothing otherwise, and safely handle idempotency.</li> </ul> <p>And guess what? That’s precisely what Pongo can do for you.</p> <p>Let’s use our favourite Shopping Cart example. Types for it could look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">interface</span> <span class="token class-name">ProductItem</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">PricedProductItem</span> <span class="token operator">=</span> ProductItem <span class="token operator">&amp;</span> <span class="token punctuation">{</span> unitPrice<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCart</span> <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> PricedProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> productItemsCount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> totalAmount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> status<span class="token operator">:</span> <span class="token string">'Opened'</span> <span class="token operator">|</span> <span class="token string">'Confirmed'</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> confirmedAt<span class="token operator">?</span><span class="token operator">:</span> Date <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span> cancelledAt<span class="token operator">?</span><span class="token operator">:</span> Date <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p><strong>We could define the basic set of operations:</strong></p> <ul> <li>adding a product item - that’s possible to not confirmed or non-existing shopping cart,</li> <li>removing a product item - that’s possible when we have enough products already in the not confirmed shopping cart,</li> <li>confirming non-empty shopping cart (we can confirm it twice, handling idempotence safely),</li> <li>cancelling opened shopping cart (we can cancel it twice, handling idempotence safely).</li> </ul> <p>The business logic could look as follows:</p> <p>Adding product item:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> addProductItem <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> now<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state <span class="token operator">&amp;&amp;</span> state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'Confirmed'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart already closed'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> now <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCart<span class="token operator">:</span> ShoppingCart <span class="token operator">=</span> state <span class="token operator">??</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> openedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'Opened'</span><span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> currentProductItem <span class="token operator">=</span> shoppingCart<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span> <span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">===</span> productItem<span class="token punctuation">.</span>productId <span class="token operator">&amp;&amp;</span> pi<span class="token punctuation">.</span>unitPrice <span class="token operator">===</span> productItem<span class="token punctuation">.</span>unitPrice<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentProductItem <span class="token operator">!==</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> currentProductItem<span class="token punctuation">.</span>quantity <span class="token operator">+=</span> productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> shoppingCart<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> shoppingCart<span class="token punctuation">.</span>totalAmount <span class="token operator">+=</span> productItem<span class="token punctuation">.</span>unitPrice <span class="token operator">*</span> productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">;</span> shoppingCart<span class="token punctuation">.</span>productItemsCount <span class="token operator">+=</span> productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">;</span> <span class="token keyword">return</span> shoppingCart<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Removing product item:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> removeProductItem <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> now<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state <span class="token operator">===</span> <span class="token keyword">null</span> <span class="token operator">||</span> state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Opened'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is not opened'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productItem <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">const</span> currentProductItem <span class="token operator">=</span> state<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span> <span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">===</span> productItem<span class="token punctuation">.</span>productId <span class="token operator">&amp;&amp;</span> pi<span class="token punctuation">.</span>unitPrice <span class="token operator">===</span> productItem<span class="token punctuation">.</span>unitPrice<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> currentProductItem <span class="token operator">===</span> <span class="token keyword">undefined</span> <span class="token operator">||</span> currentProductItem<span class="token punctuation">.</span>quantity <span class="token operator">&lt;</span> productItem<span class="token punctuation">.</span>quantity <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Not enough products in shopping carts'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> state<span class="token punctuation">.</span>totalAmount <span class="token operator">-=</span> productItem<span class="token punctuation">.</span>unitPrice <span class="token operator">*</span> productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">;</span> state<span class="token punctuation">.</span>productItemsCount <span class="token operator">-=</span> productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">;</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Confirming</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> confirm <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> <span class="token punctuation">{</span> now<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state <span class="token operator">===</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is not opened'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'Confirmed'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is empty'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> now <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> state<span class="token punctuation">.</span>status <span class="token operator">=</span> <span class="token string">'Confirmed'</span><span class="token punctuation">;</span> state<span class="token punctuation">.</span>confirmedAt <span class="token operator">=</span> now<span class="token punctuation">;</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Cancelling:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> cancel <span class="token operator">=</span> <span class="token punctuation">(</span>state<span class="token operator">:</span> ShoppingCart <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCart <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'Confirmed'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Cannot cancel confirmed Shopping Cart'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Then we can make a basic Pongo setup:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> pongoClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@event-driven-io/pongo"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> connectionString <span class="token operator">=</span> <span class="token string">"postgresql://dbuser:[email protected]:3211/mydb"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongo <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongoDb <span class="token operator">=</span> pongo<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> pongoDb<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCart<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">"shoppingCarts"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And plug our code into some request processing pipeline (e.g. web api):</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">AddProductItemRequest</span> <span class="token operator">=</span> Request<span class="token operator">&lt;</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/current/product-items'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> AddProductItemRequest<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> command <span class="token operator">=</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>clientId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span><span class="token punctuation">,</span> unitPrice<span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>unitPrice<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> shoppingCarts<span class="token punctuation">.</span><span class="token function">handle</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">addProductItem</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Ok</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>document<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>For all other endpoints the code will look the accordingly. Isn’t that nice?</p> <p><strong>I think that’s a simple and quick way to sping up a new CRUD system, or bootstrap a new Proof of Concepts.</strong></p> <p>Thoughts?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Running a regular SQL on Pongo documents]]>https://event-driven.io/en/sql_support_in_pongo/https://event-driven.io/en/sql_support_in_pongo/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 626px; height: auto" > <a class="gatsby-resp-image-link" href="/static/56dfce3259a234d6e422b2689bba799e/b09c1/2024-10-15-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADNklEQVQ4y0WS22scZRjG50r9UwpFvLYgFMEbb0QUTErrqag3ilgqYlGbw253G/e8O9mZnePO7OzszO7ObrLJJia1FJvWWCuRhpCmQVqoadKcbNp6uPvJTAQvHvjg5f19z/d8jyApJopuY1gupu1h1TxM26fmtrHrzWimGg5Oo4NpN6hoNWTVpixXKYka2bxMKiOSHMsxeiGFoJkO1ZoXLde9AKfRpt3toxq1SLdW1lhYvMmXX40gq1VcfwLVdFF0F6liI5Z1coUQOk5iLIcQwpxGgNvq0mhP0proo5oOL7/yKjPfzfPX309ZWV1l4MRJjj7/AumsGEH1agPVcJGVEGqQL1ZIZcoIdqON2+ziBz28YJLpucu8OXCSYy8dx/c81tZu0woCTgwO8Owzz/He+x8yMTUfxWPazQgqKdZ/UBXB8YLIWbM7hd/p0Zu9RCyZ4rXX3+DO+jq2U2d4dIQzn33KkSNHUTQLvz1FzW1hOS0My0PRnSjToqgh1P0uXrtHszMVQVsT00z058iLMteuL3L61CmOH3uR89+cJ1so0+3N4vpd6l4Hux5QrTXRzEOXpXEN4fC5kzQ7vQh2qD5Bb5aZucso2TRffPwRb7/7DpPT87h+gOt3IoVNqDpNtKqLpNoUQ6ATDltdvOB/l0FvhvbkTJStpalcmZ9mcPAtqrbL7PyVyGXdC2vVQrd9KoaDKFvkRRXBdHzsRgvH7xLCQ4cb2/ts7R3wYHs/0s7+Yx483GMzOj/h3v1trHo7qk9FdxivhDCNdL6MIKnV6IbQtmI4mHWf3Ud/RstXr//EzaVlFn9e4tbKHbb3HvNw94Df7m6gmTUUrUZR1MnmFcbSIvGLOYSxdIlMQSJXkFANm4XvZ2lZBsur6/x44xeuXrvBr8urEfiHhUVW1+9xqT+FnIkjlSskLxa5kMgzHEvz9VASIZ2XKYgquZKC53eRU3FK6Qx7B/+w+8dTNrb22do9YHPnEb9v7bO584SlpWU++eA0yWSGbE4iOVYknsgyPPotQkX3UE0f3Qp/y6ckmcgVnfjIKOO5DK6hoolF4kNDnPv8LOfOniERiyGpYXZOVOZU9hAaS2T5Fz7C96jVwClBAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 10 15 cover" title="2024 10 15 cover" src="/static/56dfce3259a234d6e422b2689bba799e/b09c1/2024-10-15-cover.png" srcset="/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" sizes="(max-width: 626px) 100vw, 626px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Have you heard someone say: <em>“We’ll use this tool because it requires a long onboarding and lots of memorisation?”</em></strong></p> <p>You could have seen the decision as such in hindsight, but that doesn’t happen too often intentionally. Or at least, I hope.</p> <p><strong>The Tools I built need to share a common goal: they must be accessible and enable advanced users to customise them to their needs.</strong> 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.</p> <p>That’s why I wrote <a href="/en/small_rant_about_software_design/">Small rant about Software Design</a>, 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.</p> <p><strong>That’s why, in <a href="https://github.com/event-driven-io/Pongo">Pongo</a>, 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.</strong> I think that enables me to ramp up quickly and deliver business value by deploying the first version of your software to production.</p> <p>To use <a href="https://github.com/event-driven-io/Pongo">Pongo</a>, you just need to install it:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ <span class="token function">npm</span> <span class="token function">install</span> @event-driven-io/pongo</code></pre></div> <p>Have a PostgreSQL instance working somewhere (e.g. <a href="https://hub.docker.com/_/postgres">with Docker</a>).</p> <p>Connect the client to the instance using database:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> connectionString <span class="token operator">=</span> <span class="token string">'postgresql://postgres:postgres@localhost:5432/postgres'</span><span class="token punctuation">;</span>cockroachdb <span class="token keyword">const</span> pongo <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongoDb <span class="token operator">=</span> pongo<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Define your document type:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">History</span> <span class="token operator">=</span> <span class="token punctuation">{</span> street<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Address</span> <span class="token operator">=</span> <span class="token punctuation">{</span> city<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> street<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> zip<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> history<span class="token operator">?</span><span class="token operator">:</span> History<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">User</span> <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> age<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> address<span class="token operator">?</span><span class="token operator">:</span> Address<span class="token punctuation">;</span> tags<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>And boom, you can access the collection with all the typing benefits:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> users <span class="token operator">=</span> pongoDb<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><a href="https://event-driven.io/en/pongo_strongly_typed_client/">You can even get the typed client that will make that even smoother</a>.</p> <p>Then you can insert some docs:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> docs <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">'Anita'</span><span class="token punctuation">,</span> age<span class="token operator">:</span> <span class="token number">25</span><span class="token punctuation">,</span> address<span class="token operator">:</span> <span class="token punctuation">{</span> city<span class="token operator">:</span> <span class="token string">'Wonderland'</span><span class="token punctuation">,</span> street<span class="token operator">:</span> <span class="token string">'Main St'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">'Roger'</span><span class="token punctuation">,</span> age<span class="token operator">:</span> <span class="token number">30</span><span class="token punctuation">,</span> address<span class="token operator">:</span> <span class="token punctuation">{</span> city<span class="token operator">:</span> <span class="token string">'Wonderland'</span><span class="token punctuation">,</span> street<span class="token operator">:</span> <span class="token string">'Elm St'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">'Cruella'</span><span class="token punctuation">,</span> age<span class="token operator">:</span> <span class="token number">35</span><span class="token punctuation">,</span> address<span class="token operator">:</span> <span class="token punctuation">{</span> city<span class="token operator">:</span> <span class="token string">'Dreamland'</span><span class="token punctuation">,</span> street<span class="token operator">:</span> <span class="token string">'Oak St'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">insertMany</span><span class="token punctuation">(</span>docs<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then you can filter them:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> docs <span class="token operator">=</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">{</span> address<span class="token operator">:</span> <span class="token punctuation">{</span> city<span class="token operator">:</span> <span class="token string">'Wonderland'</span><span class="token punctuation">,</span> street<span class="token operator">:</span> <span class="token string">'Elm St'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Update them:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">'Anita'</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> age<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And all that jazz. MongoDB API has its quirks, but I believe it’s flexible and familiar enough to be easy to use. But…</p> <p>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.</p> <p>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. <strong>I think that SQL is a decent fallback.</strong></p> <p>That’s why I added a few options for using SQL in the Pongo API.</p> <h2 id="sql-queries" style="position:relative;"><a href="#sql-queries" aria-label="sql queries permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>SQL queries</h2> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> plainString<span class="token punctuation">,</span> <span class="token constant">SQL</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/dumbo'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> wonderland <span class="token operator">=</span> <span class="token function">plainString</span><span class="token punctuation">(</span><span class="token string">'Wonderland'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> docs <span class="token operator">=</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data @> '{"address":{"city":"</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>wonderland<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"}}'</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>In Pongo documents are stored as regular table, the structure looks as follows:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> users <span class="token punctuation">(</span> _id <span class="token keyword">TEXT</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token keyword">data</span> JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> metadata JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'{}'</span><span class="token punctuation">,</span> _version <span class="token keyword">BIGINT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token number">1</span><span class="token punctuation">,</span> _partition <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'png_global'</span><span class="token punctuation">,</span> _archived <span class="token keyword">BOOLEAN</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">FALSE</span><span class="token punctuation">,</span> _created TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _updated TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span></code></pre></div> <p>As you see, we’re querying the nested content of the JSON kept in the <em>data</em> column. Of course, you can access any other column or use data from other tables.</p> <p>Your SQL will be placed in the WHERE statement.</p> <p>If you’re wondering what’s <strong>@event-driven-io/dumbo</strong>, then it’s a shared package between <a href="https://github.com/event-driven-io/Pongo">Pongo</a> and <a href="https://github.com/event-driven-io/emmett">Emmett</a>.</p> <h2 id="sql-updates" style="position:relative;"><a href="#sql-updates" aria-label="sql updates permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>SQL updates</h2> <p>In the same way, you can also do updates. So instead of using the object-oriented Mongo API, you can do the follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> wonderland <span class="token operator">=</span> <span class="token function">plainString</span><span class="token punctuation">(</span><span class="token string">'Wonderland'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> oz <span class="token operator">=</span> <span class="token function">plainString</span><span class="token punctuation">(</span><span class="token string">'Oz'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> docs <span class="token operator">=</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">updateMany</span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">data @> '{"address":{"city":"</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>wonderland<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"}}'</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">jsonb_set(data || '{"address":{"city":"</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>oz<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"}}'::jsonb, '{age}', to_jsonb(COALESCE((data->>'age')::NUMERIC, 0) + '1'), true)</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The first SQL will be placed in the WHERE part, and the next one will be put into the UPDATE SET pipeline.</p> <p>This will do the same as:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> docs <span class="token operator">=</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> address<span class="token operator">:</span> <span class="token punctuation">{</span> city<span class="token operator">:</span> <span class="token string">'Wonderland'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> address<span class="token operator">:</span> <span class="token punctuation">{</span> city<span class="token operator">:</span> <span class="token string">'Oz'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> age<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>My point is for the MongoDB syntax. But I’ll let you decide what looks simpler to you.</p> <h2 id="do-whatever-you-want-with-sql" style="position:relative;"><a href="#do-whatever-you-want-with-sql" aria-label="do whatever you want with sql permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Do whatever you want with SQL</h2> <p>You can also have the freehand mode, where you can draw anything or actually place any SQL. There you have it!</p> <p>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</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> pongoDb<span class="token punctuation">.</span>sql<span class="token punctuation">.</span><span class="token function">query</span><span class="token punctuation">(</span> <span class="token constant">SQL</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">SELECT _id, data->'address' as address from users</span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It will return:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"_id"</span><span class="token operator">:</span> <span class="token string">"01928fee-d18b-711b-89d7-830ba98585e8"</span><span class="token punctuation">,</span> <span class="token property">"address"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"city"</span><span class="token operator">:</span> <span class="token string">"Dreamland"</span><span class="token punctuation">,</span> <span class="token property">"street"</span><span class="token operator">:</span> <span class="token string">"Oak St"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"_id"</span><span class="token operator">:</span> <span class="token string">"01928fee-d18b-711b-89d7-755d49f389f6"</span><span class="token punctuation">,</span> <span class="token property">"address"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"city"</span><span class="token operator">:</span> <span class="token string">"Oz"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"_id"</span><span class="token operator">:</span> <span class="token string">"01928fee-d18b-711b-89d7-790f5373cd10"</span><span class="token punctuation">,</span> <span class="token property">"address"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"city"</span><span class="token operator">:</span> <span class="token string">"Oz"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">]</span></code></pre></div> <p><strong>As you can imagine, this means that you can also join with other tables, in general, whatever the SQL syntax allows you.</strong></p> <p>You can also do any other operation that changes the data like:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> pongoDb<span class="token punctuation">.</span>sql<span class="token punctuation">.</span><span class="token function">command</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">SQL UPDATE users SET data = jsonb_set(data || '{"address":{"city":"Oz"}}'::jsonb, '{age}', to_jsonb(COALESCE((data->>'age')::NUMERIC, 0) + '1'), true) || jsonb_build_object('_version', (_version + 1)::text), _version = _version + 1 WHERE data @> '{"address":{"city": "Oz"}}'</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>I think that’s pretty neat and powerful.</p> <p>So, if you’re afraid of using Pongo, you’ll get a showstopper in compatibility; hey, I have you covered!</p> <p>It’s the same if your favourite language is SQL; you can do whatever you want. Underneath, Pongo uses <a href="https://node-postgres.com">node-postgres</a> with connection pooling enabled by default, so the additional performance issue should not hit you.</p> <p>See also the videos where <a href="/en/pongo_behind_the_scenes/">I showed all of that live</a>.</p> <p>Thoughts? Yay or Nay?</p> <p>If you have more questions, join our <a href="https://discord.gg/fTpqUTMmVa">Discord server</a> and let’s tackle that together!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Pongo behind the scenes]]>https://event-driven.io/en/pongo_behind_the_scenes/https://event-driven.io/en/pongo_behind_the_scenes/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 750px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b5615e59034b5ac9b1af736ef0c81005/b13e1/2024-10-11-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADhklEQVQ4yyWR2U/bBQCAfw8++7wYo7jMZKMcc7QUpNynlHJIGVCgBxR6QCmlQH8tPbjKVZAN2FQGrIyBcoyzpSjg2JiLIdlwJvt7PiM+fPlev+QTzkKD/GY3ErO1sG5sRC9NRXbjC8qkcvSqSkSDhg5lIbJbSSi+zSBTnkbK1xKqswpoa2xEWViMqqgQU0M9NcVKhNONVQ4CIvvWZlyl31F4R0JRYhIOvZZJXz9+swFbeQn5Celk35aSd+1UdMoq/F02RIuJHpMRm16LTdeEEA0vMieK1N2TURCfRGF8AmpFJv4OK+MeEW+rFo+2hhGPi4EeBwPd9mt8Tjtih4VeSxt9lrbrusyUDIQ/t39lc/YB2mIlmpR01PIMDOUVdDZqES0W3IZGfM0aJgJuhrpt+G0mPJ0mvN2diFYzVp0eh6UNpSIPmUSG8D6ywz9nEU521lkNDvE44GPY42JI7CHo9+Bu1dLfXM+Y302gqx23xYi704zX2YXDbMasN+Jst6DKL6JIkYfw8TTC1ckBF5EXjPe7WHo0zfb6Iks/PuDx7DRDXe14musZDXgY6HP8j8tJv7MLp6Wddp0Bs1ZPq74FQ4MO4eMfR7w73uOv2A5eq4nhXjuR3WdED59zsL/K7laY5bkQgx4Rn9OBvaUVU72WiuxiFJI0pHHJlGUWo8orJV+ei/Dh9yh/x3Z5fxZhTOxl3NfL0d4qG+F5YtE1YrF1treeUSbPR/bVXeS308mSyMm9m0VafAbZKVloKiqoyCkkV6ZAeLu/zbvoLlcvj3g0Nsy428FGsJ+FLjPRvRXOz3eZGR1B8umXJH92h7qyGiYDPpqq65ElpKEuKcXcpMGgrkZXo0a42Nvi8niPy9ND1hbmCTTVsdLWwFOHmZPjXzh/uU9LZS1xn9wgJymDoZ4+Nld+ZiY4SMrNZNx2G09mQxjuq9F9X4XwZmeT1wfbvIntcxbZYaS1hWXRwcnWIq9e7bLxfBnVN2lUJibi7bAxHvAzFxoj2OPAUKpi1Otl8WEI0WpCoypHiIWfEgkv8vrwBYerS+hKVMyFprh8e8qHqwsmPB4K4m5SkyplfiLI+vJjgq4eCiRSfvphgrnJUSY8biy19dSpqhCi62tszEwxbWxD/9+pe9kos5SYNM0ERR/3M3JI//wWorWD+akQD0dHCPW7GHI6CC/MszI/jbezkzqlipZaNf8CerpfnFdeyWIAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 10 11 cover" title="2024 10 11 cover" src="/static/b5615e59034b5ac9b1af736ef0c81005/b13e1/2024-10-11-cover.png" srcset="/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" sizes="(max-width: 750px) 100vw, 750px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>If you want to make God laugh, tell him about your plans.</strong></p> <p>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.</p> <p>Okay, I also had another plan to bootstrap two projects:</p> <ul> <li><a href="https://github.com/event-driven-io/emmett">Emmett</a>,</li> <li><a href="https://github.com/event-driven-io/Pongo">Pongo</a>.</li> </ul> <p>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.</p> <p>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.</p> <p><strong>And that is how we got to Pongo Internals.</strong> That’s what I showed last week during <a href="https://www.youtube.com/watch?v=p-6fpV_GDEs">YugabytDB Community Hours</a></p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/p-6fpV_GDEs?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>And earlier on <a href="https://www.youtube.com/watch?v=P4r19rv4vOg">FerretDB Document Database Community</a>.</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/P4r19rv4vOg?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>It was tiring, as I was doing between <a href="https://notesfrompoland.com/2024/09/19/floods-prompt-polands-first-ever-state-of-natural-disaster-what-does-this-mean/">flood</a>, <a href="https://www.axoniq.io/axoniq-conference-2024">AxonIQ Conference</a> and having Tonsillitis. But I’ve made it.</p> <p><strong>It’s heartwarming that tools like FerretDB and YugabyteDB are open to collaboration.</strong> I’m all for that, so I hope you’ll see more of it.</p> <p><strong>I showcased their recent additions to Pongo like:</strong></p> <ul> <li>fresh new Pongo shell for accessing and manipulating data without setting up the project,</li> <li>filtering and updating Pongo documents with custom SQL,</li> <li>built-in optimistic concurrency without the typical MongoDB retries,</li> <li><a href="/en/pongo_strongly_typed_client/">typed client</a>,</li> <li><a href="/en/pongo_strongly_typed_client/">migrations</a>.</li> </ul> <p>I also shared how it’s working internally, so here’s a bit about the PostgreSQL JSONB magic and other decisions I’ve made.</p> <p>Check out if you want to see how sausages are made. And drop your thoughts afterwards!</p> <p><strong>I think the Pongo concept is unique and can streamline the development of new products.</strong> If you’re interested in using it or sponsoring some work, <a href="mailto:[email protected]">contact me</a>. I’m happy to jump on the call with you, showcase what’s already possible, and discuss how to help your project!</p> <p><strong>Pongo is a community project, and I believe it is a decent place to start your journey in Open Source.</strong> If you’d like to do it, join our <a href="https://discord.gg/fTpqUTMmVa">Discord server</a>, and I’ll help you jumpstart your contribution.</p> <p><strong>Read more in:</strong></p> <ul> <li><a href="/en/introducting_pongo/">Pongo - Mongo but on Postgres and with strong consistency benefits</a></li> <li><a href="/en/pongo_strongly_typed_client/">Pongo gets strongly-typed client, migrations, and command line tooling</a></li> <li><a href="/en/sql_support_in_pongo">Running a regular SQL on Pongo documents</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Pongo gets strongly-typed client, migrations, and command line tooling]]>https://event-driven.io/en/pongo_strongly_typed_client/https://event-driven.io/en/pongo_strongly_typed_client/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 750px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e864253c01617691383cd1762ddb91db/b13e1/2024-09-13-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4jAAAuIwF4pT92AAADJElEQVQozwEZA+b8ADYjFjMhEyoZDBsOBikYDE4yI2E9LGE+LF48K2lINW5KOGdEM1k/LUMtHzokFyYVCy8bEDAbDzkkFzAbEABDKx5FKh47JRcpFgsuGQ9KLSBbNyhpRzdrSTlsTT1uU0WVemxtUkFcRDBRPidDMxtHNR9KOB9KOR5EOBoAX0AuZUcxaE85WEQsW0koYlIwf21LiXZJlH1LaF9Se3p65eLbkY5xeHQ0gHo8e3U4dnA3b2ovbGcqaWgsAIOKQ42TS6GdWp+cWLy7j97byt3e1s/Ou8K9n5CKf52Wkd/Z16OlfZKaSpSaT4+YS4eNRYGFQX+AQIN9RgCVlVGbmFaioFnFv5OuqaW9ubvU09Pn5ua1srdGPz5fVEhqX1t5bk6mm12fmVmUlFCLiUmMh0x8gUF5gT8Am4BclXxVr597u6iOhH51Z19Yrqmp39rXpKGfiISEVlBLjoFynoxamohZnYddkYJTgG9Hfm1Icmg9bWg4AJCDUJ6PZKeZcaGSb311am1hQlhONltZUp2ZnaOclmxiWMa5naeaWqOXW6KXVpuOVZWGUYd4TH51QG5yMACVkkunn1urnV+knX6ZkGaEe1qSh02dkVWfl4uimYSHfE+9tqqqmWWailOikFijjFuSglCPfFSAdkVxeSwAkJBDkJA8npZhkYtWkI1Fi4NPr6pZnpNOuK6Ls6uOnpJIsqiK1cqzmINSl4dUjHpMiHlRf3NOc2tHYWkuAImFQ4+KQ5aMU5qPUJGMVpeQSqGaT6GUUqqZaLWrmq6dX6mcWc3Eq6iVapeKSH15Ulh2klZ3l118nXF0TgCCdUWIf0WXjk6OhkmJe1OVhVKLgEqailmThkrMwqiupHeViEiMgEyJfkmIfD99dlJRd6VXgKxqi62Jf1wAgIA1dHcsfXs1f3Y9fXg5g4I6ensygnlCf3Y4m5VjwbiagXc9fXQ8f3NAdHEyenczeX9jZICbXneGc3AuAHt/LXx9MoB7OoJ6PYGANYeFOomDP4mBQpKOQYmCPpaOUpCMQoV/O3x0OHp1NHx6MndyIHRzVGtsTGtrHK9AV+JkksFYAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 09 13 cover" title="2024 09 13 cover" src="/static/e864253c01617691383cd1762ddb91db/b13e1/2024-09-13-cover.png" srcset="/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" sizes="(max-width: 750px) 100vw, 750px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>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.</strong></p> <p><strong>I recently wrote on <a href="https://www.architecture-weekly.com/p/talk-is-cheap-show-me-the-numbers">Architecture Weekly about my performance investigations</a> in <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a> and <a href="https://event-driven-io.github.io/Pongo/getting-started.html">Pongo</a>.</strong> 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.</p> <p>To generate the <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a> PostgreSQL schema, I also wanted to be able to generate it for <a href="https://event-driven-io.github.io/Pongo/getting-started.html">Pongo</a> documents that I use for read models.</p> <p>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.</p> <p>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.</p> <p>The naive version could look like this:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> schema <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">'postgres'</span><span class="token punctuation">,</span> collections<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'users'</span><span class="token punctuation">,</span> <span class="token string">'orders'</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> client <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span>postgresConnectionString<span class="token punctuation">,</span> <span class="token punctuation">{</span> schema<span class="token operator">:</span> <span class="token punctuation">{</span> definition<span class="token operator">:</span> schema <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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…</p> <p>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 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy">Proxy type</a>. So I did, and bang, here we are with the new release!</p> <h2 id="strongly-typed-client" style="position:relative;"><a href="#strongly-typed-client" aria-label="strongly typed client permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Strongly Typed Client</h2> <p>The API needs to be a bit more advanced, but I think it’s still straightforward and explicit. You need to define schema like:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">User</span> <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> age<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> address<span class="token operator">?</span><span class="token operator">:</span> Address<span class="token punctuation">;</span> tags<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Customer</span> <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> address<span class="token operator">?</span><span class="token operator">:</span> Address<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> schema <span class="token operator">=</span> pongoSchema<span class="token punctuation">.</span><span class="token function">client</span><span class="token punctuation">(</span><span class="token punctuation">{</span> database<span class="token operator">:</span> pongoSchema<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">{</span> users<span class="token operator">:</span> pongoSchema<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">'users'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> customers<span class="token operator">:</span> pongoSchema<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>Customer<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">'customers'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And pass it to the client, getting the typed version.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> typedClient <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span>postgresConnectionString<span class="token punctuation">,</span> <span class="token punctuation">{</span> schema<span class="token operator">:</span> <span class="token punctuation">{</span> definition<span class="token operator">:</span> schema <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 👇 client have the same database as we defined above, and the collection</span> <span class="token keyword">const</span> users <span class="token operator">=</span> typedClient<span class="token punctuation">.</span>database<span class="token punctuation">.</span>users<span class="token punctuation">;</span> <span class="token keyword">const</span> doc<span class="token operator">:</span> User <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token function">randomUUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> name<span class="token operator">:</span> <span class="token string">'Anita'</span><span class="token punctuation">,</span> age<span class="token operator">:</span> <span class="token number">25</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> inserted <span class="token operator">=</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">insertOne</span><span class="token punctuation">(</span>doc<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 👇 yup, the collection is fully typed!</span> <span class="token keyword">const</span> pongoDoc <span class="token operator">=</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">'Anita'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>I think that’s much better developer experience, than the Mongo API that tells us always to do calls like:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> db <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token string">'postgres'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> users <span class="token operator">=</span> db<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Of course, if you like it, you can still use it. It’s great to have more options!</p> <p>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!</p> <h3 id="pongo-gets-command-line" style="position:relative;"><a href="#pongo-gets-command-line" aria-label="pongo gets command line permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Pongo gets command line</h3> <p>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.</p> <p>You can either install it globally through:</p> <div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">npm install -g @event-driven-io/pongo</code></pre></div> <p>And run it with:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">pongo</code></pre></div> <p>or without installing it globally by using <a href="https://docs.npmjs.com/cli/v8/commands/npx">npx</a></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">npx @event-driven-io/pongo</code></pre></div> <p>Cool, but what do you get from it?</p> <h2 id="sample-configuration-generation" style="position:relative;"><a href="#sample-configuration-generation" aria-label="sample configuration generation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Sample configuration generation</h2> <p>You can generate the sample config by calling:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">npx @event-driven-io/pongo config sample --generate --file ./src/pongoConfig.ts --collection <span class="token function">users</span> --collection orders</code></pre></div> <p>This command will create a config file in the selected location with predefined users and orders collections. It’ll look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> pongoSchema <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/pongo'</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">User</span> <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> description<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> date<span class="token operator">:</span> Date <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">Order</span> <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> description<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> date<span class="token operator">:</span> Date <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span> schema<span class="token operator">:</span> pongoSchema<span class="token punctuation">.</span><span class="token function">client</span><span class="token punctuation">(</span><span class="token punctuation">{</span> database<span class="token operator">:</span> pongoSchema<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">{</span> users<span class="token operator">:</span> pongoSchema<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">'users'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> orders<span class="token operator">:</span> pongoSchema<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>Order<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">'orders'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Or just print it with:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">npx @event-driven-io/pongo config sample --print --collection <span class="token function">users</span> --collection customers</code></pre></div> <p>Then, you can use adjust the generated typing and import it to your application.</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> pongoClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/pongo'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> config <span class="token keyword">from</span> <span class="token string">'./pongo.config'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongo <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> <span class="token punctuation">{</span> schema<span class="token operator">:</span> <span class="token punctuation">{</span> definition<span class="token operator">:</span> config<span class="token punctuation">.</span>schema <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3 id="performing-database-migrations" style="position:relative;"><a href="#performing-database-migrations" aria-label="performing database migrations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Performing Database Migrations</h3> <p>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!</p> <p>You can do it with new command line tooling:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">npx @event-driven-io/pongo migrate run --config ./dist/pongoConfig.js <span class="token punctuation">\</span> --connectionString postgresql://postgres:postgres@localhost:5432/postgres</code></pre></div> <p>It’ll automatically run the migrations based on the defined collections.</p> <p>If you’re unsure and don’t trust it fully, you can also add the <em>—-dryRun</em> parameter. This will run the migration in the transaction and roll it back without making any changes.</p> <p><strong>You can also use migration CLI in your build pipelines.</strong> You might not want to pass the connection string there, as it’s not secured way. No worries, you can also set <em>DB_CONNECTION_STRING</em> environment variable and run it as</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">npx @event-driven-io/pongo migrate run --config ./dist/pongoConfig.js</code></pre></div> <p>You can also run it by providing a collections list:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">npx @event-driven-io/pongo migrate run --collection <span class="token function">users</span> --collection customers <span class="token punctuation">\</span> --connectionString postgresql://postgres:postgres@localhost:5432/postgres</code></pre></div> <p>You can also just print migrations to see what schema structures will be generated by calling:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">npx @event-driven-io/pongo migrate sql --print --collection <span class="token function">users</span> --collection customers</code></pre></div> <p>It will print:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> migrations <span class="token punctuation">(</span> id <span class="token keyword">SERIAL</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> name <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">UNIQUE</span><span class="token punctuation">,</span> application <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'default'</span><span class="token punctuation">,</span> sql_hash <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">64</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">timestamp</span> <span class="token keyword">TIMESTAMP</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token keyword">CURRENT_TIMESTAMP</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> users <span class="token punctuation">(</span> _id <span class="token keyword">TEXT</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token keyword">data</span> JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> metadata JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'{}'</span><span class="token punctuation">,</span> _version <span class="token keyword">BIGINT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token number">1</span><span class="token punctuation">,</span> _partition <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'png_global'</span><span class="token punctuation">,</span> _archived <span class="token keyword">BOOLEAN</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">FALSE</span><span class="token punctuation">,</span> _created TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _updated TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> customers <span class="token punctuation">(</span> _id <span class="token keyword">TEXT</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token keyword">data</span> JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> metadata JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'{}'</span><span class="token punctuation">,</span> _version <span class="token keyword">BIGINT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token number">1</span><span class="token punctuation">,</span> _partition <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'png_global'</span><span class="token punctuation">,</span> _archived <span class="token keyword">BOOLEAN</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">FALSE</span><span class="token punctuation">,</span> _created TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _updated TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>This first <em>migrations</em> 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 <a href="https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS">Postgres Advisory Locks</a> to ensure that no migrations are happening in parallel. Safety first!</p> <p>In the future, you’ll also be able to provide your custom schema and data migrations through it!</p> <p>You already got <a href="https://github.com/event-driven-io/Pongo/blob/c3ed330a3ddf7793e6d508309cd2f729c6b19cb2/src/packages/dumbo/src/core/schema/schemaComponent.ts">Schema Components abstraction</a>. 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.</p> <h3 id="added-possibility-to-disable-generating-pongo-schema-upfront" style="position:relative;"><a href="#added-possibility-to-disable-generating-pongo-schema-upfront" aria-label="added possibility to disable generating pongo schema upfront permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Added possibility to disable generating Pongo schema upfront</h3> <p>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!</p> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> typedClient <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span>postgresConnectionString<span class="token punctuation">,</span> <span class="token punctuation">{</span> schema<span class="token operator">:</span> <span class="token punctuation">{</span> autoMigration<span class="token operator">:</span> <span class="token string">'None'</span><span class="token punctuation">,</span> definition<span class="token operator">:</span> schema <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>This will disable any automated schema generation. As a result, your application will have fewer database calls, opened connections, and overhead!</p> <h3 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h3> <p>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.</p> <p>I’m pretty happy about that.</p> <p><strong>I think that’ll also boost the developer experience even more!</strong></p> <p>Expect the follow up in Emmett.</p> <p><strong>What are your thoughts?</strong></p> <p>Read more about building Emmett and Pongo in:</p> <ul> <li><a href="/en/introducing_emmett/">Announcing Emmett! Take your event-driven applications back to the future!</a></li> <li><a href="/en/introducting_pongo/">Pongo - Mongo but on Postgres and with strong consistency benefits</a></li> <li><a href="/en/emmett_postgresql_event_store/">Event Sourcing on PostgreSQL in Node.js just became possible with Emmett</a></li> <li><a href="/en/introducing_emmett/">Testing Event Sourcing, Emmett edition</a></li> <li><a href="/en/emmett_projections_testing/">Writing and testing event-driven projections with Emmett, Pongo and PostgreSQL</a></li> <li><a href="/en/projections_and_event_metadata/">Using event metadata in event-driven projections</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/inmemory_message_bus_in_typescript/">How to build an in-memory Message Bus in TypeScript</a></li> </ul> <p>Check also the <a href="https://github.com/event-driven-io/Pongo/blob/c3ed330a3ddf7793e6d508309cd2f729c6b19cb2/samples/simple-ts/src/typedClient.ts">sample</a> and <a href="https://github.com/event-driven-io/Pongo/releases/tag/0.14.0">release notes</a></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[My Architecture Drivers]]>https://event-driven.io/en/architecture_drivers/https://event-driven.io/en/architecture_drivers/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 750px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d3ac5b456d98d37dcfd581c62fcf25d1/b13e1/2024-08-31-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 66.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4jAAAuIwF4pT92AAADIUlEQVQozwXBS2/bBAAAYP8BBAcEGodJQ5ROKhSyloX1kXWdaZPl5TgP23nUcZvYTpo4jhMnfsRxEjtb81qbLU3bpUsfsKnRxGMKIJVdygQIOMBlR7gABzhwgwuTWr4PKGTjqsB0N6r9bn2nWXg67J0+/+Ps7K/T0z/Pzv7+4WRwcFf96rO9wX77Xru62SzvbzUaWq69Vtha14GYF9S58PaGdqeuHm7qX+5Vvv2899sv3/z7z6/P//t9eNR5/LD76YOtj3qthiZu1Eq7nVualKwqrF5gAc+l0ZjneoZE243KQad68uj2J/f1R9va1x/3fv5uOBzs9Ddv7t7Ve21dYKNraralC7qcqAirDBkELNdmYPsHIa91p1PvNotNObJeSrTL7ON+64vBPTZBRIIuMgQTGGRfMAURKEkGYwRSFNM4BgE+4+s3Js7PT0+qEleW2AgMFilvXY4nXOYsCnlMlxHTZQicxhzz7jkDYgMZHI3j/hQZmjEaAOzKhYXx1/ywTSuKTBT32xb3y0IzQ0OzxlaaPihkbi77BRTmUeeqxxq2WwlkYXrqPbfDBs68D/hdNtIBoovXqIA3ap8nzXOczxlxWMApo8tgyBHY3m1tXZNbAreRWm2Gg/1iMOu2RBevXxx5A3CBc6TP7ZudWEFgKRU3Tr5jGBnxjr0NQe6A2XI/tjw47G52atWKJEjZgpgr87lWOvkhS/XjEeDFF1569fyFK1NjXthitToWX35F9MBtRRq8O368c+cJAv+Y44RwQC3mSxKfz7KyIvOSFE8xanQJMMNQYvLSNgAMS8rxyfHx6OgRhip6hYLtT0u5Z4jz8OKbpolJSczIeY7PsIoi1xu1fC4foWjg+2c/DZKx3XPnVHyJE4UQjoeJMIv5jgK+5tUpp2kWsVrY1KrEp3iOyaQZtajUGrWckKcpGnDY7DCKwSsrbhTFAxgVJehIWCOJARMTwfnxsbcEgVNFLp9lBJ7lM6ymVzRdS6fTVJQCzGaz2w0vhQJ+xEss+YlwkCKJQAi74bR77TYcD6aSNM8lxVxaymeVgrRWvyUXZJqml8PE/7lZVHGrv2UtAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 08 31 cover" title="2024 08 31 cover" src="/static/d3ac5b456d98d37dcfd581c62fcf25d1/b13e1/2024-08-31-cover.png" srcset="/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" sizes="(max-width: 750px) 100vw, 750px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I don’t feel like an authority or an expert. I prefer to think about myself as a practitioner.</strong> Our industry is filled with self-proclaimed experts; we need more doers.</p> <p><strong>For similar reasons, I was reluctant to call myself an “Architect” for a long time.</strong> Somehow, it became pejorative, but should it be? I changed my mind thanks to <a href="https://www.linkedin.com/in/jpalka/">Jarek Pałka</a>, 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.</p> <p><strong>Still, I never feel comfortable being asked about Software Architecture Drivers.</strong> How do you give someone a checklist for good software? Yet I had to answer this time, as <a href="https://www.linkedin.com/in/jedrzejewski-maciej/">Maciej Jędrzejewski</a> asked me if I could write my thoughts <a href="https://leanpub.com/master-software-architecture">to his new book</a>. 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!</p> <p>It’s funny that we called our industry <strong>SOFTware</strong>, but it’s all about making <strong>HARD decisions</strong>. 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.</p> <p><strong>For me, architecture decisions are more a process than a set of specific rules. It’s a process of answering the following questions:</strong></p> <p><strong>WHY?</strong> So, understanding the product vision and business model. Consider <a href="/en/follow_the_money/">where the money flows</a>: 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.</p> <p><strong>WHAT?</strong> 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.</p> <p><strong>HOW?</strong> 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.</p> <p><strong>WITH.</strong> 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.</p> <p><strong>Architecture is not created in a vacuum.</strong> Talk and collaborate with business, users and your technical fellows.</p> <p>Consider the <a href="/en/on_the_importance_of_shaping_the_boundaries_in_team_management/">team you (can) have</a>. 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.</p> <p><strong>And hey, let me share the secret with you: your decisions will be wrong. Mine also.</strong> 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.</p> <p>So <a href="/en/why_are_we_afraid_of_our_decisions/">don’t be afraid to make decisions</a>, 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.</p> <p>We should <a href="/en/removability_over_maintainability/">optimise not for maintainability but for removability</a>. 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.</p> <p><strong>What are your architecture drivers? Or better, what’s your process?</strong></p> <p><strong>Check also Maciej’s book <a href="https://leanpub.com/master-software-architecture">“Master Software Architecture Book”</a>.</strong> 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.</p> <p>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.</p> <p>If you need even more architecture benchmarks, I have gathered some of my past articles on my general approach to architecture and software design:</p> <ul> <li><a href="/en/architect_manifesto/">Architect Manifesto</a></li> <li><a href="/en/how_to_design_software_architecture_pragmatically/">How to design software architecture pragmatically</a></li> <li><a href="/en/removability_over_maintainability/">Removability over Maintainability</a></li> <li><a href="en/the_risk_of_ignoring_risks/">The risk of ignoring risks</a></li> <li><a href="/en/why_are_we_afraid_of_our_decisions/">Why are we afraid of our decisions?</a></li> <li><a href="/en/chesterton_fence_and_software_architecture/">What do the British writer and his fence have to do with Software Architecture?</a></li> <li><a href="/en/follow_the_money/">Follow the money to get a better design</a></li> <li><a href="/en/a_few_words_on_communication/">A few words on communication</a></li> <li><a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">How to successfully do documentation without a maintenance burden?</a></li> <li><a href="/en/the_magic_is_that_there_is_no_magic/">The magic is that there is no magic. Or how to understand design patterns</a></li> <li><a href="/en/how_to_solve_complicated_problems/">Not all issues are complex, some are complicated. Here’s how to deal with them</a></li> <li><a href="/en/on_the_importance_of_shaping_the_boundaries_in_team_management/">On the importance of setting boundaries in team management</a></li> <li><a href="/en/dune_and_long_term_goals/">What Dune can tell us about setting our goals</a></li> <li><a href="/en/stacking_the_bricks/">Stacking the bricks in the software development process</a></li> <li><a href="/en/holy_graal_syndrome/">The Holy Grail syndrome</a></li> <li><a href="/en/dive_a_bit_deeper_look_a_bit_wider/">Dive a bit deeper, look a bit wider</a></li> <li><a href="/en/what_does_mr_bean_opening_the_car_have_to_do_with_programming/">What does Mr Bean opening the car have to do with programming?</a></li> <li><a href="/en/what_does_a_construction_failure_have_to_do_with_our_authorities/">What does a construction failure have to do with our authorities?</a></li> </ul> <p><strong>And check <a href="https://www.architecture-weekly.com/">Architecture Weekly</a> where I’m showing my less-event-driven face every week!</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Using event metadata in event-driven projections]]>https://event-driven.io/en/projections_and_event_metadata/https://event-driven.io/en/projections_and_event_metadata/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d3b3f36478db62c37631cf815bf85de9/a331c/2024-08-25-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4dAAAuHQEHEELWAAADGElEQVQozwXBWW/aBgAAYL+nU7Sp69JOPSgNMSExYDAxh7ExNgaCAYOJOQMGh8uDQFqSQkJLQqgZpFXTsnSVukyVKu2I1KoP7bRNU6dW2lse1i3a39n3AVeMUJyEzg7SWym3DkPxEONk6SUGN5F2yAozqUhQFLhKzuijbNC1U0V4txt/UaGOC8STrBOYx1DBi/x7PzOo8awYN5h07jBTb8sWD273kyjlmNeDC7AODdAOBDybSGcPM6eD6Ls70btJJ6BxoCGX8a9ebFAJQpjFqNdUOl+VNstLDOH0EVwhsUTjEAqbKNyBgB/HidNB7G0n8ssw/3Q7C0BuLMmY3u8KjYTbbDV0N5IvTg59EY8Rs9jctvJmWTk+OPrhMZ9fMUPqD8Ps615q/dba8NHuj6+eAqDVFGcsH0b5nVJIA+vsZo2foxjOg1AYzboJlmJ574oYXfK5rLDmfT/5ZiitbtVKndr4YRe4qJ1dZW3/TMr9WmQO1hFOI7FMkiEaJlA64PIJAXZlmQhSepfdYrjxZz/5dlyKVcXibTnF4cBVUJ3yIn8frLbz/ut6rctP0KzLH3I7nCYbiTr8JOYlEBd6HdLiyNzHb0ovFUlvXnA54dxqCACNOtYK/trhEpRxanqKJMydRppFZn//ad+wqFZp1SrVJdXslfOXLmhVM90MtRHDPpk+p7ebY4kAwEVpn1WnpPG7MidmvHs7az8f7yqS97/fRg0pIKU9BYGoZulSmmQJPY8ttKVgf9AYPO4dPVOAWxUemZ1p8bb9prCR89XSHjlFN8rRRoHdu5m8v5n4+nbmSTc1aXEhAmKgy8WgdXM9WS+E6lIYeP69cvnzc2lCl6MMa+7FOrO4L3qO9oqTHfG7bnbS5Eet9EjmRnIkSJpsZrBeCCu9aq/KKWUWOBzdJJC5thxrJqntHNOL29sC9myd/bYVe9TkD2rhYZndSlMP7qw1xWWBdazwVDUbkDm76IGBnVZu3K/dk8PjvPtoPTwsLh8WmZOm99U2d09i/jgZ9yuhrXJUjDO4BZxXX/zyi+nPPp2auTB949rM/4GzDSjupWh0AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 08 25 cover" title="2024 08 25 cover" src="/static/d3b3f36478db62c37631cf815bf85de9/a331c/2024-08-25-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Some time ago, I wrote about the dangers that come from the <a href="/en/i_will_just_add_one_more_field/">I’ll just add one more field”</a> attitude.</strong> Have you heard about the <a href="https://web.archive.org/web/20090418141450/http://www.theatlantic.com//doc//198203//broken-windows">Broken Window Theory</a>? Authors (James Q. Wilson and George L. Kelling) wrote:</p> <blockquote> <p>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.)</p> </blockquote> <p>Adam Tornhill tells us to treat our <a href="https://pragprog.com/titles/atcrime2/your-code-as-a-crime-scene-second-edition/">code as a crime scene</a>, 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!</p> <p>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):</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemAddedToShoppingCart</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> addedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> removedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartConfirmed</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartCancelled</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartCancelled'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> cancelledAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> ProductItemAddedToShoppingCart <span class="token operator">|</span> ProductItemRemovedFromShoppingCart <span class="token operator">|</span> ShoppingCartConfirmed <span class="token operator">|</span> ShoppingCartCancelled<span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">ProductItem</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">PricedProductItem</span> <span class="token operator">=</span> ProductItem <span class="token operator">&amp;</span> <span class="token punctuation">{</span> unitPrice<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p><strong>They show what can happen in our shopping cart’s lifetime.</strong> They’re <a href="/en/events_should_be_as_small_as_possible/">as small as possible, but not smaller</a>. 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 <a href="/en/on_putting_stream_id_in_event_data/">the danger of slicing too much from events</a>.</p> <p><strong>Of course, this is the grey area and a careful act of balancing where to put where.</strong> 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?</p> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ClientShoppingSummary</span> <span class="token operator">=</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> pending<span class="token operator">:</span> PendingSummary <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span> confirmed<span class="token operator">:</span> ConfirmedSummary<span class="token punctuation">;</span> cancelled<span class="token operator">:</span> CancelledSummary<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingSummary</span> <span class="token operator">=</span> <span class="token punctuation">{</span> productItemsCount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> totalAmount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">PendingSummary</span> <span class="token operator">=</span> ShoppingSummary <span class="token operator">&amp;</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ConfirmedSummary</span> <span class="token operator">=</span> ShoppingSummary <span class="token operator">&amp;</span> <span class="token punctuation">{</span> cartsCount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">CancelledSummary</span> <span class="token operator">=</span> ShoppingSummary <span class="token operator">&amp;</span> <span class="token punctuation">{</span> cartsCount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p><strong>The id of our read model is equal to the client id. Every client will have a single summary.</strong></p> <p>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.</p> <p>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?</p> <p><strong>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.</strong> Tying those two models together and decreasing scaling and isolation. That’s an important aspect, <a href="https://event-driven.io/en/projections_and_read_models_in_event_driven_architecture/#scaling-and-data-isolation">I wrote about it here</a>.</p> <p><strong>Still, if we think that business-wise, data should always be there, then we could use event metadata.</strong> 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.</p> <p><strong>We can look at our endpoints and commands that initiate business logic, resulting in events.</strong> For instance, if we look at:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">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</code></pre></div> <p>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.</p> <p>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.</p> <p>Having that, we could consider making the client id a part of the shopping cart event metadata. This could look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartEventMetadata</span> <span class="token operator">=</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemAddedToShoppingCart</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> addedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> ShoppingCartEventMetadata <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> removedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> ShoppingCartEventMetadata <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartConfirmed</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> ShoppingCartEventMetadata <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartCancelled</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartCancelled'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> cancelledAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> ShoppingCartEventMetadata <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>If we <a href="https://github.com/event-driven-io/emmett/blob/49d2b1fb1444f0fb129c327a27c06b7ec7c94ff2/samples/webApi/expressjs-with-postgresql/src/shoppingCarts/businessLogic.ts#L25">define also such metadata for our commands</a>, we can pass it as that:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"> <span class="token comment">// Confirm Shopping Cart</span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/current/confirm'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> Request<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> clientId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>clientId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">getShoppingCartId</span><span class="token punctuation">(</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>clientId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> command<span class="token operator">:</span> ConfirmShoppingCart <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ConfirmShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// 👇 See what we did here</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> clientId<span class="token punctuation">,</span> now<span class="token operator">:</span> <span class="token function">getCurrentTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">confirm</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">NoContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Business logic</span> <span class="token keyword">export</span> <span class="token keyword">const</span> confirm <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> ConfirmShoppingCart<span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartConfirmed <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Opened'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is not opened'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> totalQuantityOfAllProductItems <span class="token operator">=</span> <span class="token function">sum</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>totalQuantityOfAllProductItems <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is empty'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// 👇 See what we did here</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> metadata<span class="token operator">!</span><span class="token punctuation">.</span>clientId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>I’m using <a href="https://event-driven-io.github.io/emmett/getting-started.html#webapi-definition">TypeScript, Express.js and Emmett</a>, but I’m sure that you can translate that to your favourite language and tech stack.</p> <p>As we now have the client ID in metadata, we could use it for the event to read model correlation. For instance, using <a href="/en/emmett_projections_testing/">Emmett+Pongo projections</a>:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> clientShoppingSummaryCollectionName <span class="token operator">=</span> <span class="token string">'ClientShoppingSummary'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> clientShoppingSummaryProjection <span class="token operator">=</span> <span class="token function">pongoMultiStreamProjection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> collectionName<span class="token operator">:</span> clientShoppingSummaryCollectionName<span class="token punctuation">,</span> <span class="token comment">// 👇 See what we did here</span> <span class="token function-variable function">getDocumentId</span><span class="token operator">:</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=></span> event<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>clientId<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">,</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> <span class="token string">'ShoppingCartCancelled'</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re saying that to find the document ID for each shopping cart event, you can use <em>event.metadata.clientId</em>.</p> <p>Then, the projection definition can look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> evolve <span class="token operator">=</span> <span class="token punctuation">(</span> document<span class="token operator">:</span> ClientShoppingSummary <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> event<span class="token punctuation">,</span> metadata <span class="token punctuation">}</span><span class="token operator">:</span> ShoppingCartEvent<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ClientShoppingSummary <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> summary<span class="token operator">:</span> ClientShoppingSummary <span class="token operator">=</span> document <span class="token operator">??</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> metadata<span class="token operator">!</span><span class="token punctuation">.</span>clientId<span class="token punctuation">,</span> pending<span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span> confirmed<span class="token operator">:</span> initialSummary<span class="token punctuation">,</span> cancelled<span class="token operator">:</span> initialSummary<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>summary<span class="token punctuation">,</span> pending<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> event<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> summary<span class="token operator">:</span> summary<span class="token punctuation">.</span>pending<span class="token punctuation">,</span> <span class="token keyword">with</span><span class="token operator">:</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'adding'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>summary<span class="token punctuation">,</span> pending<span class="token operator">:</span> <span class="token punctuation">{</span> cartId<span class="token operator">:</span> event<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> <span class="token operator">...</span><span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> summary<span class="token operator">:</span> summary<span class="token punctuation">.</span>pending<span class="token punctuation">,</span> <span class="token keyword">with</span><span class="token operator">:</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'removing'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>summary<span class="token punctuation">,</span> pending<span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span> confirmed<span class="token operator">:</span> <span class="token punctuation">{</span> cartsCount<span class="token operator">:</span> summary<span class="token punctuation">.</span>confirmed<span class="token punctuation">.</span>cartsCount <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> summary<span class="token operator">:</span> summary<span class="token punctuation">.</span>confirmed<span class="token punctuation">,</span> <span class="token keyword">with</span><span class="token operator">:</span> summary<span class="token punctuation">.</span>pending<span class="token operator">!</span><span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'adding'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartCancelled'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>summary<span class="token punctuation">,</span> pending<span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span> cancelled<span class="token operator">:</span> <span class="token punctuation">{</span> cartsCount<span class="token operator">:</span> summary<span class="token punctuation">.</span>confirmed<span class="token punctuation">.</span>cartsCount <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> summary<span class="token operator">:</span> summary<span class="token punctuation">.</span>confirmed<span class="token punctuation">,</span> <span class="token keyword">with</span><span class="token operator">:</span> summary<span class="token punctuation">.</span>pending<span class="token operator">!</span><span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'adding'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">return</span> summary<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> initialSummary <span class="token operator">=</span> <span class="token punctuation">{</span> cartsCount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> withAdjustedTotals <span class="token operator">=</span> <span class="token punctuation">(</span>options<span class="token operator">:</span> <span class="token punctuation">{</span> summary<span class="token operator">:</span> ShoppingSummary <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span> <span class="token keyword">with</span><span class="token operator">:</span> PricedProductItem <span class="token operator">|</span> ShoppingSummary<span class="token punctuation">;</span> by<span class="token operator">:</span> <span class="token string">'adding'</span> <span class="token operator">|</span> <span class="token string">'removing'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> summary<span class="token operator">:</span> document<span class="token punctuation">,</span> by <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">const</span> totalAmount <span class="token operator">=</span> <span class="token string">'totalAmount'</span> <span class="token keyword">in</span> options<span class="token punctuation">.</span>with <span class="token operator">?</span> options<span class="token punctuation">.</span>with<span class="token punctuation">.</span>totalAmount <span class="token operator">:</span> options<span class="token punctuation">.</span>with<span class="token punctuation">.</span>unitPrice <span class="token operator">*</span> options<span class="token punctuation">.</span>with<span class="token punctuation">.</span>quantity<span class="token punctuation">;</span> <span class="token keyword">const</span> productItemsCount <span class="token operator">=</span> <span class="token string">'productItemsCount'</span> <span class="token keyword">in</span> options<span class="token punctuation">.</span>with <span class="token operator">?</span> options<span class="token punctuation">.</span>with<span class="token punctuation">.</span>productItemsCount <span class="token operator">:</span> options<span class="token punctuation">.</span>with<span class="token punctuation">.</span>quantity<span class="token punctuation">;</span> <span class="token keyword">const</span> plusOrMinus <span class="token operator">=</span> by <span class="token operator">===</span> <span class="token string">'adding'</span> <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>document<span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token punctuation">(</span>document<span class="token operator">?.</span>totalAmount <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> totalAmount <span class="token operator">*</span> plusOrMinus<span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> <span class="token punctuation">(</span>document<span class="token operator">?.</span>productItemsCount <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> productItemsCount <span class="token operator">*</span> plusOrMinus<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>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. <a href="/en/event_transformations_and_loosely_coupling/">in-flight event transformations</a>). Still, I hope this article gives you enough context for the tradeoff analysis and making educated decisions in your design.</p> <p>Read also more in:</p> <ul> <li><a href="/en/i_will_just_add_one_more_field/">Anti-patterns in event modelling - I’ll just add one more field</a>.</li> <li><a href="/en/on_putting_stream_id_in_event_data/">Stream ids, event types prefixes and other event data you might not want to slice off</a></li> <li><a href="/en/events_should_be_as_small_as_possible/">Events should be as small as possible, right?</a></li> <li><a href="/en/projections_and_read_models_in_event_driven_architecture/">Event transformations, a tool to keep our processes loosely coupled</a></li> <li><a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">How to create projections of events for nested object structures?</a></li> <li><a href="/en/event_transformations_and_loosely_coupling">Guide to Projections and Read Models in Event-Driven Architecture</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Writing and testing event-driven projections with Emmett, Pongo and PostgreSQL]]>https://event-driven.io/en/emmett_projections_testing/https://event-driven.io/en/emmett_projections_testing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5566e1eee42e2f76ee638e555fc4d646/a331c/2024-08-14-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4dAAAuHQEHEELWAAADJElEQVQozwEZA+b8AAEBAQAAAQICAgAAADQ5P4qPl6KmsF1iawYHBwAAAFZhdo6XqX+GmJSft4yVqnSDmnaGnnGCmXuLoZiowQAICgsNDxAAAABvdn/c6PHBvL6/np/UtbB2bGoACiRMY4OirsGTobejr8Rug51Kc5tReaFRfKdtiaeXpbsAkaazbX6DAAAAztfd4uPksZ6cwZugvYd4472mSmWJNlmGprLFn6q/n6W3eYebS3ejUHmiVoCpeYibZ2dyAOP4/5KhpUZKTe/4+t7X2MWam8mdorqDgeG3ooyOmjtKZ1NVX09IS0o8P0Y+RFhthWeEokNvnWludywoMADZ7vWmu8OwucDp9fjVycnLn6HJnKW+fHW4i2yeel6ScFd3Y1x5cnl3ZWeDZWGYd2+JhohtiKSPeXGMdWwAyNrl8Pv+3+/31eXx3dzgw6CitpOXsIByo4x6jYaCcGVfmH5z2MO568GYuoFPsZJ3knJUaGBbXEc1TzUjAPn//+77/ubw9tfd5uj3/6uKhW9GN4yCfZGovG99jTsnIYx2ba+SiejKrpR3XBwLAxYKBxMIAhIKBiUaFQDy/f/l9vzp8/jf3uLv/v+pm5gdFxGHgIOToLGWpLVPPjmkkY29n43UsZTlvpZBKyAOBgIfEAkaCwMdEAUA7fn85fP65vL44dzd8/n9tsDHR0lEbWZqoKKxrbK1hX+DoZWVgGxf5semuIZcZlBEPTlAPzg8PDg+ODY9AOv5/ePy+uXt89fU1eXv97rCy4KBiI6QoY+Qm5V2bquNh5F9eIhhRaZ1S4VTMIJnU0VEVU5KWElMXVpaaQDj9Pvh8Pnl7PHExM7Y5fClrb+Ri56Mgo2/rq7IlI6YYVF2TkKKVjpyPyGTYC+1hVZGNC4uKjFxe41oZW0A5vT74fH55u/1w8bOvcfWtsHVtbbDVkI/pnt3iGRca0U7Ig8JVDAqh0gjlWQspXZIl2pHUUA9R0lSFhIVAOv4/Of2+9Xh68jS3MjV38LL1c/h7ouRnVU7NXdsZrCan6BqaIxYV10iDm5BG4ZdOGhGLSwfFzk5PUZDSlfvniG7swEoAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 08 14 cover" title="2024 08 14 cover" src="/static/5566e1eee42e2f76ee638e555fc4d646/a331c/2024-08-14-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>In the <a href="/en/emmett_postgresql_event_store/">previous article</a>, I told what happened when <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a> and <a href="https://event-driven-io.github.io/Pongo/getting-started.html">Pongo</a> walked into a bar. In other words, I announced that you can now do Event Sourcing in Node.js on top of PostgreSQL.</strong> 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.</p> <p><strong>Let’s explore this in more detail today, focusing on the single-stream projection.</strong></p> <p>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 <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a></p> <p>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 <a href="/en/projections_and_read_models_in_event_driven_architecture/">Guide to Projections and Read Models in Event-Driven Architecture</a>.</p> <p>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!</p> <h2 id="single-stream-projections-with-pongo" style="position:relative;"><a href="#single-stream-projections-with-pongo" aria-label="single stream projections with pongo permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Single Stream Projections with Pongo</h2> <p>Let’s install Emmett PostgreSQL package:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ <span class="token function">npm</span> <span class="token function">add</span> @event-driven-io/emmett-postgresql</code></pre></div> <p>Behind the scenes, it’ll also install Emmett and Pongo as peer dependencies, so you don’t need to install them separately.</p> <p>Now we’ll define a simplified Shopping Cart flow, expressing it by the events that can happen:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token keyword">type</span> <span class="token class-name">Event</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemAdded</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> addedAt<span class="token operator">:</span> Date <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemRemoved</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> PricedProductItemm<span class="token punctuation">;</span> removedAt<span class="token operator">:</span> Date <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">PricedProductItem</span> <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> unitPrice<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">DiscountApplied</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'DiscountApplied'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> percent<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> couponId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> discountedAt<span class="token operator">:</span> Date <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartConfirmed</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> confirmedAt<span class="token operator">:</span> Date <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> ProductItemAdded <span class="token operator">|</span> ProductItemRemoved <span class="token operator">|</span> DiscountApplied <span class="token operator">|</span> ShoppingCartConfirmed<span class="token punctuation">;</span></code></pre></div> <p>We’ll skip the business logic part; you can learn about it in detail in <a href="https://event-driven-io.github.io/emmett/getting-started.html#commands">Emmett’s Getting Started Guide</a>. Let’s go straight to thinking about read models.</p> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">type</span> <span class="token class-name">ShoppingCartSummary</span> <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItemsCount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> totalAmount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Now, let’s define how we’d like to apply those events:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> evolve <span class="token operator">=</span> <span class="token punctuation">(</span> document<span class="token operator">:</span> ShoppingCartSummary <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> ProductItemAdded <span class="token operator">|</span> ProductItemRemoved <span class="token operator">|</span> DiscountApplied<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartSummary <span class="token operator">=></span> <span class="token punctuation">{</span> document <span class="token operator">=</span> document <span class="token operator">??</span> <span class="token punctuation">{</span> totalAmount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span> productItemsCount<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAdded'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token operator">:</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'adding'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemoved'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token operator">:</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'removing'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'DiscountApplied'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>document<span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token punctuation">(</span>document<span class="token punctuation">.</span>totalAmount <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">100</span> <span class="token operator">-</span> event<span class="token punctuation">.</span>percent<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> withAdjustedTotals <span class="token operator">=</span> <span class="token punctuation">(</span> options<span class="token operator">:</span> <span class="token punctuation">{</span> document<span class="token operator">:</span> ShoppingCartSummary<span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> by<span class="token operator">:</span> <span class="token string">'adding'</span> <span class="token operator">|</span> <span class="token string">'removing'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> by <span class="token punctuation">}</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">const</span> plusOrMinus <span class="token operator">=</span> by <span class="token operator">===</span> <span class="token string">'adding'</span> <span class="token operator">?</span> <span class="token number">1</span><span class="token operator">:</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>document<span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> document<span class="token punctuation">.</span>totalAmount <span class="token operator">+</span> productItem<span class="token punctuation">.</span>unitPrice <span class="token operator">*</span> productItem<span class="token punctuation">.</span>quantity <span class="token operator">*</span> plusOrMinus<span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> document<span class="token punctuation">.</span>productItemsCount <span class="token operator">+</span> productItem<span class="token punctuation">.</span>quantity <span class="token operator">*</span> plusOrMinus<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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.</p> <p>The next step is to define our Pongo projection. We do it by:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> pongoSingleStreamProjection <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> collectionName <span class="token operator">=</span> <span class="token string">'shopping_carts_summary'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCartSummaryProjection <span class="token operator">=</span> <span class="token function">pongoSingleStreamProjection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> <span class="token string">'ProductItemRemoved'</span><span class="token punctuation">,</span> <span class="token string">'DiscountApplied'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> collectionName<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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).</p> <p>If you don’t like getting a null document in the evolve function, then you can also provide the initial state:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> shoppingCartSummaryProjection <span class="token operator">=</span> <span class="token function">pongoSingleStreamProjection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> <span class="token string">'ProductItemRemoved'</span><span class="token punctuation">,</span> <span class="token string">'DiscountApplied'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> collectionName<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> <span class="token function-variable function">initialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> totalAmount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span> productItemsCount<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then your evolve can skip the setup step and look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> evolve <span class="token operator">=</span> <span class="token punctuation">(</span> document<span class="token operator">:</span> ShoppingCartSummary<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> ProductItemAdded <span class="token operator">|</span> ProductItemRemoved <span class="token operator">|</span> DiscountApplied<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartSummary <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAdded'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token operator">:</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'adding'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemoved'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token operator">:</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'removing'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'DiscountApplied'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>document<span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token punctuation">(</span>document<span class="token punctuation">.</span>totalAmount <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">100</span> <span class="token operator">-</span> event<span class="token punctuation">.</span>percent<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Emmett will provide the initial state if the document with id equal to the stream name doesn’t exist.</p> <p><strong>We need to complete registration by passing it to event store options:</strong></p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> projection <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> getPostgreSQLEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> connectionString <span class="token operator">=</span> <span class="token string">"postgresql://dbuser:[email protected]:3211/mydb"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> <span class="token function">getPostgreSQLEventStore</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> <span class="token punctuation">{</span> projections<span class="token operator">:</span> projections<span class="token punctuation">.</span><span class="token function">inline</span><span class="token punctuation">(</span><span class="token punctuation">[</span> shoppingCartSummaryProjection<span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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.</p> <p>Sounds cool; now we can append a few events through regular event store append events api and query results using Pongo:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> getPostgreSQLEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/pongo'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> connectionString <span class="token operator">=</span> <span class="token string">"postgresql://dbuser:[email protected]:3211/mydb"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongo <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCartsSummary <span class="token operator">=</span> pongo<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">collection</span><span class="token punctuation">(</span><span class="token string">'shopping_carts_summary'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> summary <span class="token operator">=</span> <span class="token keyword">await</span> shoppingCartsSummary<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id <span class="token operator">:</span> <span class="token string">'shopping_cart-123'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Cool, but manually querying data isn’t the best long-term solution. Wouldn’t it be possible to test it automatically? Yes, it would.</p> <p><strong>That’s why Emmett gives you a built-in way to test projections.</strong></p> <h2 id="testing-projections" style="position:relative;"><a href="#testing-projections" aria-label="testing projections permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testing projections</h2> <p>In the same way as testing other parts of your development flow (read more in <a href="/en/testing_event_sourcing_emmett_edition/">Testing Event Sourcing, Emmett edition</a>).</p> <p>Before we start, let’s install PostgreSQL Test Containers. We’ll need them soon:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ <span class="token function">npm</span> <span class="token function">add</span> @testcontainers/postgresql</code></pre></div> <p>As I described in <a href="/en/testing_event_driven_projections/">How to test event-driven projections</a>, 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.</p> <p>Now, let’s start with the setup:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> PostgreSQLProjectionSpec <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> PostgreSqlContainer<span class="token punctuation">,</span> StartedPostgreSqlContainer<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@testcontainers/postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> after<span class="token punctuation">,</span> before<span class="token punctuation">,</span> beforeEach<span class="token punctuation">,</span> describe<span class="token punctuation">,</span> it <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:test'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> v4 <span class="token keyword">as</span> uuid <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'uuid'</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Shopping carts summary'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> postgres<span class="token operator">:</span> StartedPostgreSqlContainer<span class="token punctuation">;</span> <span class="token keyword">let</span> connectionString<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">let</span> given<span class="token operator">:</span> PostgreSQLProjectionSpec<span class="token operator">&lt;</span>ProductItemAdded <span class="token operator">|</span> ProductItemRemoved <span class="token operator">|</span> DiscountApplied<span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">let</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token function">before</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> postgres <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">PostgreSqlContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> connectionString <span class="token operator">=</span> postgres<span class="token punctuation">.</span><span class="token function">getConnectionUri</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> given <span class="token operator">=</span> PostgreSQLProjectionSpec<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token punctuation">{</span> projection<span class="token operator">:</span> shoppingCartShortInfoProjection<span class="token punctuation">,</span> connectionString<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">beforeEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>shoppingCartId <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">shoppingCart-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> expectPongoDocuments <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Shopping carts summary'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...) test setup</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'first added product creates document'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> unitPrice<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> productId<span class="token operator">:</span> <span class="token string">'shoes'</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">100</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> streamName<span class="token operator">:</span> shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span> expectPongoDocuments <span class="token punctuation">.</span><span class="token generic-function"><span class="token function">fromCollection</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartSummary<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">'shopping_carts_summary'</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">withId</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">toBeEqual</span><span class="token punctuation">(</span><span class="token punctuation">{</span> productItemsCount<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token number">10000</span><span class="token punctuation">,</span> appliedDiscounts<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Tests are written in <a href="/en/behaviour_driven_design_is_not_about_tests/">Behaviour-Driven Design</a> style, using Given/When/Then:</p> <ul> <li><strong>Given</strong> existing stream of events,</li> <li><strong>When</strong> new events are added,</li> <li><strong>Then</strong> read model is updated.</li> </ul> <p>We do it this way, as we expect read models to be ONLY updated through upcoming events.</p> <p>Emmett gives you also out-of-the-box test assertion helpers to make testing Pongo easier.</p> <p>You may have noticed that our Given is empty, so let’s fix it!</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">import</span> <span class="token punctuation">{</span> eventsInStream<span class="token punctuation">,</span> newEventsInStream<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Shopping carts summary'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...) test setup</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'applies discount for existing shopping cart with product'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> couponId <span class="token operator">=</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">eventsInStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> unitPrice<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> productId<span class="token operator">:</span> <span class="token string">'shoes'</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">100</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span> <span class="token function">newEventsInStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'DiscountApplied'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> percent<span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span> couponId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span> expectPongoDocuments <span class="token punctuation">.</span><span class="token generic-function"><span class="token function">fromCollection</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartShortInfo<span class="token operator">></span></span></span><span class="token punctuation">(</span> shoppingCartShortInfoCollectionName<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">withId</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">toBeEqual</span><span class="token punctuation">(</span><span class="token punctuation">{</span> productItemsCount<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token number">9000</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You probably noticed the next helpers: _eventsInStream_and <em>newEventsInStream</em>. 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!</p> <p>You could even write tests like this:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Shopping carts summary'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...) test setup</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'applies discount for existing shopping cart with product'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">shoppingCartWithProductItem</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> unitPrice<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span> <span class="token function">discountApplied</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> percent<span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span> <span class="token function">summaryUpdated</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> productItemsCount<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token number">9000</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The setup methods are defined below:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Shopping carts summary'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...) test</span> <span class="token keyword">const</span> <span class="token function-variable function">shoppingCartWithProductItem</span> <span class="token operator">=</span> <span class="token punctuation">(</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> productItem<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>PricedProductItem<span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">eventsInStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> unitPrice<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> productId<span class="token operator">:</span> <span class="token string">'shoes'</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token operator">...</span>productItem <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token function-variable function">discountApplied</span> <span class="token operator">=</span> <span class="token punctuation">(</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> discount<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> couponId<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">newEventsInStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'DiscountApplied'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> percent<span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span> couponId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">...</span>data <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">const</span> <span class="token function-variable function">summaryUpdated</span> <span class="token operator">=</span> <span class="token punctuation">(</span>shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> expected<span class="token operator">:</span> ShoppingCartSummary<span class="token punctuation">)</span> <span class="token operator">=></span> expectPongoDocuments <span class="token punctuation">.</span><span class="token generic-function"><span class="token function">fromCollection</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartShortInfo<span class="token operator">></span></span></span><span class="token punctuation">(</span> shoppingCartShortInfoCollectionName<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">withId</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">toBeEqual</span><span class="token punctuation">(</span>expected<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That approach is more focused on the business flow and allows us to reuse those test setups. Still, the outcome is the same.</p> <p><strong>Can read model data be only updated? Not only that, you can also delete it.</strong> 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.</p> <p>To have that, we need to update our evolve function by adding <em>ShopingCartConfirmed</em> event:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">const</span> evolve <span class="token operator">=</span> <span class="token punctuation">(</span> document<span class="token operator">:</span> ShoppingCartSummary<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> ShoppingCartEvent<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartSummary <span class="token operator">|</span> <span class="token keyword">null</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// &lt;= see here</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAdded'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token operator">:</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'adding'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemoved'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">withAdjustedTotals</span><span class="token punctuation">(</span><span class="token punctuation">{</span> document<span class="token punctuation">,</span> productItem<span class="token operator">:</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> by<span class="token operator">:</span> <span class="token string">'removing'</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'DiscountApplied'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>document<span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token punctuation">(</span>document<span class="token punctuation">.</span>totalAmount <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">100</span> <span class="token operator">-</span> event<span class="token punctuation">.</span>percent<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token comment">// &lt;= and here</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We made the shopping cart confirmed event return null. That means that the document should be removed. Emmett is using the Pongo’s <em>handle</em> method internally:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> collection <span class="token operator">=</span> pongo<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>Document<span class="token operator">></span></span></span><span class="token punctuation">(</span>collectionName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> event <span class="token keyword">of</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span><span class="token function">handle</span><span class="token punctuation">(</span><span class="token function">getDocumentId</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>document<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">evolve</span><span class="token punctuation">(</span>document<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>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?</p> <p>The test checking will look as follows:</p> <div class="gatsby-highlight" data-language="ts"><pre class="language-ts"><code class="language-ts"><span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Shopping carts summary'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> given<span class="token operator">:</span> PostgreSQLProjectionSpec<span class="token operator">&lt;</span>ShoppingCartEvent<span class="token operator">></span><span class="token punctuation">;</span> <span class="token comment">// (...) test setup</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'confirmed event removes read mode for shopping cart with applied discount'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> couponId <span class="token operator">=</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">eventsInStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> unitPrice<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> productId<span class="token operator">:</span> <span class="token string">'shoes'</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">100</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'DiscountApplied'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> percent<span class="token operator">:</span> <span class="token number">10</span><span class="token punctuation">,</span> couponId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span> <span class="token function">newEventsInStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> confirmedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span> expectPongoDocuments <span class="token punctuation">.</span><span class="token generic-function"><span class="token function">fromCollection</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartShortInfo<span class="token operator">></span></span></span><span class="token punctuation">(</span> shoppingCartShortInfoCollectionName<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">withId</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">notToExist</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// &lt;= see this</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The pattern looks the same, but the assertion is different.</p> <p><strong>I hope that this article shows you how powerful the combination of Emmett, Pongo, and PostgreSQL is.</strong> 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.</p> <p>Go ahead, play with it, check it, and drop me a line. <a href="https://discord.gg/fTpqUTMmVa">Joining our Discord</a> is an excellent way to do this.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Event Sourcing on PostgreSQL in Node.js just became possible with Emmett]]>https://event-driven.io/en/emmett_postgresql_event_store/https://event-driven.io/en/emmett_postgresql_event_store/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/21de7aa5676ecf81e6a64ae81d26984d/a331c/2024-07-12-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4eAAAuHgGqqWO7AAADn0lEQVQ4yxXMaU+bBQDA8efFpuAGha605SqwCmxlbaEnMCpXD6CF8gCFUso1YKvc2MmxroCUU8IWyCI4UHRThmfim2nipiYucbrEGI1xMb5Usxli4if4m/0+wE8wpCZgTEvEnCGhKF2CNVuGR6dgstnI9ZCTtyNtfLo2wK8fzvPJtTCXRQNurQSH9hTOIjl2fQoOrRxDRiLSF44j2FQnceZJqdXIqSvKpLogA2+hkrlgGdHAS7iNOfgtahaGG/nts0V2Rhz0V+RQq5FSniuhXCPF8SzWpWDNSUKo1ytpNmbiM2cS8prwmHMRDWmERSNjdj2xBhtLLhM2tYzHP9zji+0p5lv0jHm0tBZnUZ4rxZqTSJVOhr1IgTAgFuM1Z9NkUtFRno9Tq0I0pjPl0bPpsXHY4yVaUUR3cTZ///kHXx9ssCBqWO6yEW0v5lJNAabUREpyJLgMCoSZ7mpEQzpiYRq1BUrsZ1MJWDNY7iwjWmPhpr+GcUsed2bb+efHfd6bEZn3GZlrszDdqKevWo3rjIJmi4oqrQJhbsBBk05Bs16JI0+K7bSUAcc57qxf5o3+JgK6dNotWRzG/Ny/MchXWwPcGHISaSpkY8jL+kQbwSoTwXI1FWeTERZCTnx6BY1nJHgNKtbCveyvTXGwNc/e3Bj16mQajOnEukvZC9exP1XP5mA1sY4Sfnn0gCdH/3L09Am///yI2EQPwsIlF6ImmV5bPrtrV9ldnWQ7Ns7e0jhbi2EClQba7YWs91ey0lnKao+NhYCVvbDIzgd3aZm7xYWV23z3+C/uffwWQqS7koa8BNYmLvDu1iKvjXawOTvC/voVNiIXmR3vpaehhCmxkEW/mQW/mWhjAR+tDHHw+QPck9vUTe1y/+FP7K+MIUwHy/Dp5GwvX+F6dIi5YT+rM4PsrEyzFO5isMvNoL+a0VoNvaUZ9J5X4StI5NZ6hGcOv/ye2Dt3+e/oKaHGMoRJv4U+h57ttatEX/YxEnAxPdzJzdcjzIR89DZXMdzppubFVGzKOErk8ZiTj2HMU3MxNMhE+FX2b7/Pw2+/4bxKgvBKi5Exv4M31yOMdzjpri/m2vwYs6MdDPhd9LfXEmy0YVAmUalKxp2dRKtOiVqWwHNxJ5DLFWSfzkV+8nn85kyEcKuViS4Pi+Od9HgsBFwGIqFWOtxW+oJe+oIexEotmpQ4qvJl2HNSCJhyKVOdIu34MVLi45ElnOBcaiJ+axb/A5NYDTrR73RDAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 07 12 cover" title="2024 07 12 cover" src="/static/21de7aa5676ecf81e6a64ae81d26984d/a331c/2024-07-12-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Last week, I announced <a href="https://github.com/event-driven-io/Pongo">Pongo</a> - Mongo, but it was on PostgreSQL. So, the Node.js library allows using PostgreSQL as a document database.</p> <p><strong>Today, I have at least an equally big announcement: I released the PostgreSQL event store for <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a>. Boom!</strong></p> <p>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!</p> <p>Read more:</p> <ul> <li><a href="/en/introducing_emmett/">Announcing Emmett! Take your event-driven applications back to the future!</a></li> <li><a href="/en/introducing_emmett/">Testing Event Sourcing, Emmett edition</a></li> </ul> <p>How to use it? Pretty simple. Start with installing npm package:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">$ <span class="token function">npm</span> <span class="token function">add</span> @event-driven-io/emmett-postgresql</code></pre></div> <p>Then setup event store using connection string to PostgreSQL:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getPostgreSQLEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> connectionString <span class="token operator">=</span> <span class="token string">"postgresql://dbuser:[email protected]:3211/mydb"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> <span class="token function">getPostgreSQLEventStore</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Internally, it uses the <a href="https://node-postgres.com/">node-postgres</a> package with connection pooling. So you don’t need to do much more. Well, maybe besides gracefully closing it on the application closure:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">await</span> eventStore<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Cool, but what you can do with it? Check <a href="https://event-driven-io.github.io/emmett/getting-started.html#event-store">Emmett docs</a>. The same you can do with EventStoreDB storage you can do with PostgreSQL!</p> <p>Or you can actually do more with PostgreSQL, as…</p> <p>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.</p> <p>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:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">ShoppingCartShortInfo</span> <span class="token operator">=</span> <span class="token punctuation">{</span> productItemsCount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> totalAmount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Transformation function could look like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> evolve <span class="token operator">=</span> <span class="token punctuation">(</span> document<span class="token operator">:</span> ShoppingCartShortInfo <span class="token operator">|</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> ProductItemAdded <span class="token operator">|</span> DiscountApplied<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartShortInfo <span class="token operator">=></span> <span class="token punctuation">{</span> document <span class="token operator">=</span> document <span class="token operator">??</span> <span class="token punctuation">{</span> productItemsCount<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAdded'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> totalAmount<span class="token operator">:</span> document<span class="token punctuation">.</span>totalAmount <span class="token operator">+</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>price <span class="token operator">*</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> productItemsCount<span class="token operator">:</span> document<span class="token punctuation">.</span>productItemsCount <span class="token operator">+</span> event<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'DiscountApplied'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>document<span class="token punctuation">,</span> totalAmount<span class="token operator">:</span> <span class="token punctuation">(</span>document<span class="token punctuation">.</span>totalAmount <span class="token operator">*</span> <span class="token punctuation">(</span><span class="token number">100</span> <span class="token operator">-</span> event<span class="token punctuation">.</span>percent<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>It’ll be run for each event of type <em>ProductItemAdded</em> and <em>DiscountApplied</em> that’s appended to the event store.</p> <p>Let’s say that you’d like to use Pongo and store it as a document in PostgreSQL, then you can define projection as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> shoppingCartShortInfoCollectionName <span class="token operator">=</span> <span class="token string">'shoppingCartShortInfo'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCartShortInfoProjection <span class="token operator">=</span> <span class="token function">pongoSingleStreamProjection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> collectionName<span class="token operator">:</span> shoppingCartShortInfoCollectionName<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> <span class="token string">'DiscountApplied'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>and register it through event store options:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> projection <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> getPostgreSQLEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-postgresql'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> connectionString <span class="token operator">=</span> <span class="token string">"postgresql://dbuser:[email protected]:3211/mydb"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> <span class="token function">getPostgreSQLEventStore</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> <span class="token punctuation">{</span> projections<span class="token operator">:</span> projections<span class="token punctuation">.</span><span class="token function">inline</span><span class="token punctuation">(</span><span class="token punctuation">[</span> shoppingCartShortInfoProjection<span class="token punctuation">,</span> customProjection<span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re saying that we’d like to update the <em>shoppingCartShortInfo</em> collection using the <em>evolve</em> function for the following set of event types.</p> <p>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.</p> <p>It look’s as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> collection <span class="token operator">=</span> pongo<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>Document<span class="token operator">></span></span></span><span class="token punctuation">(</span>collectionName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> event <span class="token keyword">of</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> collection<span class="token punctuation">.</span><span class="token function">handle</span><span class="token punctuation">(</span><span class="token function">getDocumentId</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>document<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">evolve</span><span class="token punctuation">(</span>document<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>If you’re wondering what <em>getDocumentId</em> is, then for <em>pongoSingleStreamProjection</em>, it’ll automatically use the stream name as the document id.</p> <p>Suppose you’d like to customise it, e.g. to match events from different streams. In that case, you can use the <em>pongoMultiStreamProjection</em> definition, which allows you to specify the document ID matcher for each event. For instance:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> shoppingCartShortInfoCollectionName <span class="token operator">=</span> <span class="token string">'shoppingCartShortInfo'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> getDocumentId <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">{</span>type<span class="token punctuation">}</span><span class="token operator">:</span> ProductItemAdded <span class="token operator">|</span> DiscountApplied<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span><span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAdded'</span><span class="token operator">:</span> <span class="token keyword">return</span> event<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>streamName<span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'DiscountApplied'</span><span class="token operator">:</span> <span class="token keyword">return</span> event<span class="token punctuation">.</span>metadata<span class="token punctuation">.</span>streamName<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCartShortInfoProjection <span class="token operator">=</span> <span class="token function">pongoSingleStreamProjection</span><span class="token punctuation">(</span><span class="token punctuation">{</span> collectionName<span class="token operator">:</span> shoppingCartShortInfoCollectionName<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> canHandle<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'ProductItemAdded'</span><span class="token punctuation">,</span> <span class="token string">'DiscountApplied'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> getDocumentId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You can also do a free-hand projection using <em>pongoProjection</em> that takes the following handler:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token punctuation">(</span>pongo<span class="token operator">:</span> PongoClient<span class="token punctuation">,</span> events<span class="token operator">:</span> ReadEvent<span class="token operator">&lt;</span>EventType<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span></code></pre></div> <p>Cool, isn’t it?</p> <p>**Read more details in the follow up article <a href="/en/emmett_projections_testing">Writing and testing event-driven projections with Emmett, Pongo and PostgreSQL</a></p> <p>Of course, those are still experimental features; they need to be optimised, tested extensively, etc. But they work, which makes me happy.</p> <p><strong>As you see, I’m quite thrilled that I could deliver it, as this is a big milestone.</strong> This will enable many people to finally do Event Sourcing in Node.js using PostgreSQL and have basic building blocks.</p> <p><strong>All of that wouldn’t be possible with my recent changes to Pongo.</strong></p> <ol> <li><strong>I managed to close the basic coverage of document manipulation methods.</strong> I added initial versions of what was initially missing: replaceOne, drop, rename, countDocuments, count, estimatedDocumentCount, findOneAndDelete, findOneAndReplace, findOneAndUpdate, etc.</li> </ol> <p>Now, a bigger portion that were made as preparations to Emmett PostgreSQL projections you just learned about:</p> <ol start="2"> <li> <p><strong>Added option to inject external connection pool and db client to Pongo collection as the first step for transaction handling.</strong> 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.</p> </li> <li> <p><strong>Strengthened the schema and updated the id from UUID to text.</strong> 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.</p> </li> </ol> <p>Also, thanks to the Franck Pachot contribution, we confirmed that Pongo is compatible not only with vanilla Postgres but also databases like Yugabyte!</p> <p><strong>It was just a week, but I’m extremely happy with how Pongo was taken by the community.</strong> I got to <a href="https://news.ycombinator.com/item?id=40897518">HackerNews front page</a> and got over 900 GitHub stars!</p> <p><strong>Now it’s the time for Emmett!</strong> Synergy with Pongo should help with that.</p> <p>What a week! It’s easy to forget that Pongo was released just 7 days ago!</p> <p><strong>Now I can go to vacations, next blog will be in August!</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Pongo - Mongo but on Postgres and with strong consistency benefits]]>https://event-driven.io/en/introducting_pongo/https://event-driven.io/en/introducting_pongo/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a85203c2ed52d88de17d5d70d5209510/a331c/2024-07-07-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4eAAAuHgGqqWO7AAACIklEQVQ4y5VTS28SURS+P4AECfIsMDN3HkJoQJOmU0jdFNuG0iEwRBxmQCYBXIjpglhjXKh1IRt3Ju5JjGtXXTRxady5c83WGH8CyWfuBSyPmtTFl3Nz7rnf+e55EElSsAhRlLHq+x8QQZAgipQTzclW7VXJZFkDpcuxDETTNqGqKVCqglJtZtU1gsWHiYSEYDCKWEzgsYt3pFh6hP3DDsz7z7GTMxAIBBGJxCEIdI2YWaYsnc7Asmzk87vY2EhAUW5dEh4cHMKyHFgNF51OD8ViEfV6HZnMbYRCUcTj4hKZ1+vD2dkbjMdjNBoNEEIQi4tcAK9hu/0QzaYDwziGZVlwXRcnJ09QqVRQKNyDru/8VcgUh8NRjEYjTCYTvHz1Gpoiwzg6gqomIQoUxLZtVKtVmGYN7GyaJkqlY9h2gyvd2ytwQkbGVKRSmxi+HeLnr9/4+uUCHz5+xvm3H+h1e/DfDIEwJUyZYZTRbDbRarWQTKZ50SORGAdrAsP8635/EO9eDDAc9PH+0zlGF9/xdDCAzxeYftl123AcB7VaDZb1gJ+Zv9vt8DtWknK5PGuSDIlqyGoJUFFC/3Efp6fPoOs53nWytaUjn78LXc+jUNjH9naOg/mYzeV2wWKy2TtLHRckFYIow+O5AY/Hi3A4xhMS1jlFSc6yq7y406GdziU7MxLW7asGno0Mw3weyXTCLzflX5gSyGsbtRpDrruj193xP5BiohPfrnjyAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 07 07 cover" title="2024 07 07 cover" src="/static/a85203c2ed52d88de17d5d70d5209510/a331c/2024-07-07-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Flexibility or Consistency?</strong> Why not have both? Wouldn’t it be great to have MongoDB flexible schema and PostgreSQL consistency?</p> <p>MongoDB is a decent database, but it gives headaches with its eventual consistency handling. I wrote about it a few times in past:</p> <ul> <li><a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">Dealing with Eventual Consistency and Idempotency in MongoDB projections</a></li> <li><a href="/en/long_polling_and_eventual_consistency/">Long-polling, how to make our async API synchronous</a></li> <li><a href="/en/testing_event_driven_projections/">How to test event-driven projections</a></li> </ul> <p>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.</p> <p><strong>On Friday, I decided to spend my working day on the small proof of concept that I called <a href="https://github.com/event-driven-io/Pongo">Pongo</a>.</strong></p> <p>What’s <a href="https://github.com/event-driven-io/Pongo">Pongo</a>?</p> <p><strong>It’s a MongoDB-compliant wrapper on top of Postgres.</strong></p> <p>You can setup it like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> pongoClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@event-driven-io/pongo"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> connectionString <span class="token operator">=</span> <span class="token string">"postgresql://dbuser:[email protected]:5432/yourdb"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongoClient <span class="token operator">=</span> <span class="token function">pongoClient</span><span class="token punctuation">(</span>postgresConnectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongoDb <span class="token operator">=</span> pongoClient<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> users <span class="token operator">=</span> pongoDb<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">"users"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It will start internally with a PostgreSQL connection pool connected to your selected database.</p> <p>Having that, you can then perform operations like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> anita <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">"Anita"</span><span class="token punctuation">,</span> age<span class="token operator">:</span> <span class="token number">25</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// Inserting</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">insertOne</span><span class="token punctuation">(</span>roger<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">insertOne</span><span class="token punctuation">(</span>cruella<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> insertedId <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">insertOne</span><span class="token punctuation">(</span>alice<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> anitaId <span class="token operator">=</span> insertedId<span class="token punctuation">;</span> <span class="token comment">// Finding by Id</span> <span class="token keyword">const</span> anitaFromDb <span class="token operator">=</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id<span class="token operator">:</span> anitaId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Updating</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id<span class="token operator">:</span> anitaId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> age<span class="token operator">:</span> <span class="token number">31</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Deleting</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">deleteOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id<span class="token operator">:</span> cruella<span class="token punctuation">.</span>_id <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Finding by Id</span> <span class="token keyword">const</span> anitaFromDb <span class="token operator">=</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id<span class="token operator">:</span> anitaId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Finding more</span> <span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">{</span> age<span class="token operator">:</span> <span class="token punctuation">{</span> $lt<span class="token operator">:</span> <span class="token number">40</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Internally, it’ll set up the collection as the PostgreSQL table with the key-value structure:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> <span class="token string">"YourCollectionName"</span> <span class="token punctuation">(</span> _id <span class="token keyword">TEXT</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token keyword">data</span> JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> metadata JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'{}'</span><span class="token punctuation">,</span> _version <span class="token keyword">BIGINT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token number">1</span><span class="token punctuation">,</span> _partition <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token string">'png_global'</span><span class="token punctuation">,</span> _archived <span class="token keyword">BOOLEAN</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token boolean">FALSE</span><span class="token punctuation">,</span> _created TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _updated TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">DEFAULT</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span></code></pre></div> <p><strong>Essentially, it treats PostgreSQL as a key/value database.</strong> Sounds familiar? Yet, it’s a similar concept to <a href="https://martendb.io/">Marten</a> or, more correctly, to AWS DocumentDB (see <a href="https://www.enterprisedb.com/blog/documentdb-really-postgresql">here</a> or <a href="https://news.ycombinator.com/item?id=18870397">there</a>, they seem to be using Mongo syntactic sugar on top of AuroraDB with Postgres).</p> <p>I explained in <a href="/en/strategy_on_migrating_relational_data_to_document_based/">general strategy for migrating relational data to document-based</a> 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.</p> <p>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.</p> <p><strong>The binary format of JSONB means that data is pre-parsed, allowing faster read and write operations than text-based JSON.</strong> 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 <a href="https://pganalyze.com/blog/gin-index#postgresql-jsonb-and-gin-indexes">GIN and GiST indexes, making searches within JSONB documents much quicker and more efficient</a>.</p> <p>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.</p> <p>This flexibility, performance, and consistency combination makes PostgreSQL with JSONB a powerful tool. There are <a href="https://info.enterprisedb.com/rs/069-ALB-339/images/PostgreSQL_MongoDB_Benchmark-WhitepaperFinal.pdf">benchmarks showing that it can be even faster than MongoDB</a>.</p> <p>Still, the syntax is not the most pleasant (to say mildly). Just <a href="https://www.postgresql.org/docs/current/functions-json.html">check the docs</a> or see what Pongo does behind the scenes.</p> <p><strong>For instance, the MongoDB update syntax:</strong></p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> pongoCollection <span class="token operator">=</span> pongoDb<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">"users"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> someId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $push<span class="token operator">:</span> <span class="token punctuation">{</span> tags<span class="token operator">:</span> <span class="token string">"character"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>will be translated to:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">UPDATE</span> <span class="token string">"users"</span> <span class="token keyword">SET</span> <span class="token keyword">data</span> <span class="token operator">=</span> jsonb_set<span class="token punctuation">(</span><span class="token keyword">data</span><span class="token punctuation">,</span> <span class="token string">'{tags}'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">COALESCE</span><span class="token punctuation">(</span><span class="token keyword">data</span><span class="token operator">-</span><span class="token operator">></span><span class="token string">'tags'</span><span class="token punctuation">,</span> <span class="token string">'[]'</span>::jsonb<span class="token punctuation">)</span> <span class="token operator">||</span> to_jsonb<span class="token punctuation">(</span><span class="token string">'character'</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">WHERE</span> _id <span class="token operator">=</span> <span class="token string">'137ef052-e41c-428b-b606-1c8070a47eda'</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Or for query:</strong></p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> pongoCollection <span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token string-property property">"address.history"</span><span class="token operator">:</span> <span class="token punctuation">{</span> $elemMatch<span class="token operator">:</span> <span class="token punctuation">{</span> street<span class="token operator">:</span> <span class="token string">"Elm St"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>will result in:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> <span class="token keyword">data</span> <span class="token keyword">FROM</span> <span class="token string">"users"</span> <span class="token keyword">WHERE</span> jsonb_path_exists<span class="token punctuation">(</span> <span class="token keyword">data</span><span class="token punctuation">,</span> <span class="token string">'$.address.history[*] ? (@.street == "Elm St")'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>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:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> MongoClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@event-driven-io/pongo"</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> v4 <span class="token keyword">as</span> uuid <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"uuid"</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">User</span> <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> age<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> connectionString <span class="token operator">=</span> <span class="token string">"postgresql://dbuser:[email protected]:3211/mydb"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongoClient <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">MongoClient</span><span class="token punctuation">(</span>postgresConnectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> pongoDb <span class="token operator">=</span> pongoClient<span class="token punctuation">.</span><span class="token function">db</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> users <span class="token operator">=</span> pongoDb<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">collection</span><span class="token generic class-name"><span class="token operator">&lt;</span>User<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token string">"users"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> anita <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token string">"Anita"</span><span class="token punctuation">,</span> age<span class="token operator">:</span> <span class="token number">25</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// Inserting</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> insertedId <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">insertOne</span><span class="token punctuation">(</span>alice<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> anitaId <span class="token operator">=</span> insertedId<span class="token punctuation">;</span> <span class="token comment">// Updating</span> <span class="token keyword">await</span> users<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id<span class="token operator">:</span> anitaId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> age<span class="token operator">:</span> <span class="token number">31</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Deleting</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">deleteOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id<span class="token operator">:</span> cruella<span class="token punctuation">.</span>_id <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Finding by Id</span> <span class="token keyword">const</span> anitaFromDb <span class="token operator">=</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id<span class="token operator">:</span> anitaId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Finding more</span> <span class="token keyword">const</span> users <span class="token operator">=</span> <span class="token keyword">await</span> pongoCollection<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">{</span> age<span class="token operator">:</span> <span class="token punctuation">{</span> $lt<span class="token operator">:</span> <span class="token number">40</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And guess why? You can already use it by installing the package:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">install</span> @event-driven-io/pongo</code></pre></div> <p>Why did I create Pongo? There are two reasons.</p> <p><strong>First, I’ll need it for <a href="https://event-driven-io.github.io/emmett/">Emmett</a> read models.</strong> I was a bit silent about updates to Emmett as I’m working on adding subscriptions and streaming capabilities. The ongoing <a href="https://github.com/event-driven-io/emmett/pull/76">Pull Request</a> went a bit out of hand, and I was a bit worn out.</p> <p><strong>Thus, the second reason. Sometimes, you just need to have fun.</strong> 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 <a href="https://disney.fandom.com/wiki/Pongo">one of my favourite children’s movie characters</a>.</p> <p><strong>Is it production-ready?</strong> 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 <a href="https://en.wikipedia.org/wiki/Pareto_principle">Pareto principle</a> 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.</p> <p>Still, it can already do the most needed operations, so you should be fine with trying it!</p> <p>I’m planning to do a series of articles on the internals of building such a tool!</p> <p><strong>I’m curious about your thoughts on it. Yay or nay?</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Filtering EventStoreDB subscriptions by event types]]>https://event-driven.io/en/filtering_eventstoredb_subscriptions_by_event_types/https://event-driven.io/en/filtering_eventstoredb_subscriptions_by_event_types/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ac7ebf197e1a9ec135ecce515f6c4edb/a331c/2024-06-30-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4eAAAuHgGqqWO7AAACVElEQVQoz5WSz0/TABTHv+26H90YMxshOHQwxi+Hri0/lgYXixdICLAs60LZ1q6/1lEYFAgmsoge5GCCd4lKPGhIPHgyciDePIh69GA8+094MRinxkTRxE/eS97lffPy8gH+B4KA62f9jsfriUQiXq83HA5TFBUIBNxuN03TwWDQ5/MRBHFq4I9uCbQKwoQo5gVBCIVCgiDE4/FIJMJx3OXxcVCe3laICWTjmOlG0AOQJFzkt81EGuwUNZS8lMlk0uk0x3E8z4+MjIyNjfE8z6dHQfq3R3GyhS/r+LwKpg0Yv4LtfdI58NUeYemBvyjnZ2fndF2TZXllZcWyLE3TlpeX1xxn8CKT8ENPkqV+SH044wVuHwYOPnH3jhYaR65JuV3MLUiSZBiGaZq2bauqqut6qVQyNDXUEdP68LFEvCvgVQ5dQeDu656d/fkbT4ZvHUYF8ezmTaO6MaMqpm0v1ut10zQtyyqXy4tWlZ+YLCZwnCdezuH5NM61AKtPsXPUff2Ff/dt+7VnAedhp2xmy0Vlacl2HKdWq1UqlUKhoKmV3sHkQCuMJCEPoNjfPDvcCXPPtfeh9/579s5xSN4crpmOUpElSarX67Isq6qay+VUuewNRxscTraIXw8DEGOx+6Z9/XFbZoGSlJk1Z0NRFE3TbNs2TVPX9UajURDzHZ3nYzSyPcR0F6ZiCLqb0gAYukp0XEB/nDMMU1XV+XlJ1/VKE1EUFUWpVqvRaLQpGXG6fX4/nUqlWJZlGIbjuO8zy7KpVIphGJqmAVB/6kmQ+FviP/gKX1GYZc5k5i4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 06 30 cover" title="2024 06 30 cover" src="/static/ac7ebf197e1a9ec135ecce515f6c4edb/a331c/2024-06-30-cover.png" srcset="/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" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Regular expressions are one of the classic examples of hate and hate relationships.</strong> 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?</p> <p>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.</p> <p>Still, undeniably, regular expressions are useful and powerful. Let me show you an example, but be careful; I warned you already!</p> <p><strong>We’ll use in this article knowledge from my two other articles:</strong></p> <ul> <li><a href="/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/">Persistent vs catch-up, EventStoreDB subscriptions in action</a></li> <li><a href="/en/event_stores_are_key_value_stores/">Event stores are key-value databases, and why that matters</a></li> <li><a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a></li> <li><a href="how_to_map_event_type_by_convention/">Mapping event type by convention</a></li> </ul> <p><strong>Don’t want to dive deep into them? Fine, here’s the TLDR:</strong></p> <ul> <li>EventStoreDB is a database that has the durable append-only log as the physical structure where events are stored,</li> <li>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. <em>OrderConfirmed</em>, <em>OrderPaymentRecorded</em>, etc.)</li> <li>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.</li> <li>EventStoreDB has a functionality called <a href="https://developers.eventstore.com/clients/grpc/subscriptions.html#subscribing-from-the-start">Catch-up subscriptions</a>, which allows you to subscribe to push notifications about new events. You can subscribe to all events or those coming from specific streams.</li> </ul> <p><strong>Today, we’ll focus on subscribing to all events and how to filter them by event type.</strong></p> <p>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 <em>$all</em> 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.</p> <p><strong>By default, you’ll get all events, whether you need them or not, including internal events used for internal store processing.</strong> 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 <a href="https://developers.eventstore.com/clients/grpc/subscriptions.html#server-side-filtering">server-side filtering</a>. It will perform the filtering during moving cursor, skipping unwanted events without sending them to our application.</p> <p>Out of the box, you can:</p> <ul> <li>filter out system events,</li> <li>filter by the stream name prefix,</li> <li>filter by the event type prefix.</li> </ul> <p>That covers default cases, especially if you have conventional-based naming. You can include module prefixes and stream category prefixes into the name and get a specific set of events, but what if you’d like to get events with specific types? What if those events don’t share a common prefix because they’re from different (sub)modules or stream categories? Then you have one more option.</p> <p><strong>You can filter out events using our inglorious regular expressions!</strong> Yes, I know how that sounds, but please stay with me.</p> <p>The subscription with filter looks like that in C#:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> filterOptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SubscriptionFilterOptions</span><span class="token punctuation">(</span>EventTypeFilter<span class="token punctuation">.</span><span class="token function">RegularExpression</span><span class="token punctuation">(</span><span class="token string">"SOME REGEX"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> subscription <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">SubscribeToAll</span><span class="token punctuation">(</span> FromAll<span class="token punctuation">.</span>Start<span class="token punctuation">,</span> <span class="token named-parameter punctuation">filterOptions</span><span class="token punctuation">:</span> cancellationToken<span class="token punctuation">:</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Regular expressions allow us to provide the OR operator. Let’s say that we want to get <em>InvoiceIssued</em> and <em>UserEmailProvided</em> events. Regex for it could look as</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">^(InvoiceIssued|UserEmailProvided)$"</code></pre></div> <p>Not that hard, aye? Let’s try to generalise it then:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EventFilters</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Regex</span> <span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> values<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token string">"^("</span> <span class="token operator">+</span> <span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span><span class="token string">"|"</span><span class="token punctuation">,</span> values<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>Regex<span class="token punctuation">.</span>Escape<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">")$"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Simple code, we’re just joining the strings with <strong><em>|</em></strong> separator, escaping the text if it includes some reserved regular expression characters like dot.</p> <p>We can pass the array of strings and build regular expressions from them. We can call it as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> filterRegex <span class="token operator">=</span> EventFilters<span class="token punctuation">.</span><span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span><span class="token string">"InvoiceIssued"</span><span class="token punctuation">,</span> <span class="token string">"UserEmailProvided"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> filterOptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SubscriptionFilterOptions</span><span class="token punctuation">(</span>EventTypeFilter<span class="token punctuation">.</span><span class="token function">RegularExpression</span><span class="token punctuation">(</span>filterRegex <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We could also wrap that into a single method:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EventFilters</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Regex</span> <span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> values<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token string">"^("</span> <span class="token operator">+</span> <span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span><span class="token string">"|"</span><span class="token punctuation">,</span> values<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>Regex<span class="token punctuation">.</span>Escape<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">")$"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IEventFilter</span> <span class="token function">OneOfEventTypes</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> values<span class="token punctuation">)</span> <span class="token operator">=></span> EventTypeFilter<span class="token punctuation">.</span><span class="token function">RegularExpression</span><span class="token punctuation">(</span><span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span>values<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Now, if we have the event type mapper that’s mapping CLR type into the event type name (known from <a href="how_to_map_event_type_by_convention/">mentioned earlier article</a>), we could also add:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EventFilters</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Regex</span> <span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> values<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token string">"^("</span> <span class="token operator">+</span> <span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span><span class="token string">"|"</span><span class="token punctuation">,</span> values<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>Regex<span class="token punctuation">.</span>Escape<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">")$"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Regex</span> <span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span><span class="token class-name">EventTypeMapper</span> eventTypeMapper<span class="token punctuation">,</span> <span class="token keyword">params</span> <span class="token class-name">Type<span class="token punctuation">[</span><span class="token punctuation">]</span></span> eventTypes<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span>eventTypes<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>eventTypeMapper<span class="token punctuation">.</span>ToName<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Regex</span> <span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">Type<span class="token punctuation">[</span><span class="token punctuation">]</span></span> eventTypes<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span>EventTypeMapper<span class="token punctuation">.</span>Instance<span class="token punctuation">,</span> eventTypes<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IEventFilter</span> <span class="token function">OneOfEventTypes</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> values<span class="token punctuation">)</span> <span class="token operator">=></span> EventTypeFilter<span class="token punctuation">.</span><span class="token function">RegularExpression</span><span class="token punctuation">(</span><span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span>values<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IEventFilter</span> <span class="token function">OneOfEventTypes</span><span class="token punctuation">(</span><span class="token class-name">EventTypeMapper</span> eventTypeMapper<span class="token punctuation">,</span> <span class="token keyword">params</span> <span class="token class-name">Type<span class="token punctuation">[</span><span class="token punctuation">]</span></span> eventTypes<span class="token punctuation">)</span> <span class="token operator">=></span> EventTypeFilter<span class="token punctuation">.</span><span class="token function">RegularExpression</span><span class="token punctuation">(</span><span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span>eventTypeMapper<span class="token punctuation">,</span> eventTypes<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IEventFilter</span> <span class="token function">OneOfEventTypes</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">Type<span class="token punctuation">[</span><span class="token punctuation">]</span></span> eventTypes<span class="token punctuation">)</span> <span class="token operator">=></span> EventTypeFilter<span class="token punctuation">.</span><span class="token function">RegularExpression</span><span class="token punctuation">(</span><span class="token function">OneOfEventTypesRegex</span><span class="token punctuation">(</span>eventTypes<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And use it as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> eventTypeMapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventTypeMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> eventTypeMapper<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddCustomMap</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>InvoiceIssued<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token string">"InvoiceIssued"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> eventTypeMapper<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddCustomMap</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserEmailProvided<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token string">"UserEmailProvided"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> filterOptions <span class="token operator">=</span> EventFilters<span class="token punctuation">.</span><span class="token function">OneOfEventTypes</span><span class="token punctuation">(</span>eventTypeMapper<span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">ShoppingCartOpened</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Or conventional based</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> filterOptions <span class="token operator">=</span> EventFilters<span class="token punctuation">.</span><span class="token function">OneOfEventTypes</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">InvoiceIssued</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">UserEmailProvided</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And that’s all, folks; it wasn’t as hard as we thought it would be!</p> <p>Still, not to leave you with the impression that Regular Expressions are pleasant, let me finish with an additional example. If I’d like to filter out (so not get) system events AND my specific event called, e.g. <em>CheckpointStored</em>, then the filter would look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EventFilters</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token class-name">Regex</span> ExcludeSystemAndCheckpointEventsRegex <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token string">@"^(?!\$)(?!"</span> <span class="token operator">+</span> Regex<span class="token punctuation">.</span><span class="token function">Escape</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">CheckpointStored</span><span class="token punctuation">)</span><span class="token punctuation">.</span>FullName<span class="token operator">!</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">"$).+"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And I’ll leave you with that, allowing you to think for a moment about how it works (or pasting it to ChatGPT asking for help). How would you generalise to filter out more than one event?</p> <p><strong>If you’d like to expand and align knowledge around EventStoreDB (or Event Sourcing in general), <a href="mailto:[email protected]">contact me!</a> Besides blogging, I’m also doing consulting and <a href="https://event-driven.io/en/training/">trainings</a>. Happy to help you and your team!</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to automatically setup pgAdmin with a Docker database]]>https://event-driven.io/en/automatically_connect_pgadmin_to_database/https://event-driven.io/en/automatically_connect_pgadmin_to_database/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/02f729724eee309d0e698546710c2397/a331c/2024-06-21-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4fAAAuHwF47oFfAAAB3UlEQVQ4y62STWsTURSG8ydcuHTjn3AvdaFCQcSfIRZ1oSlaI+1exNJIqlgNIgYTK9RN0++IFqpNNC1pJ0mrme8kk4zJJDPzyEwbO40FBXvg3Ht573nPPee+J8QxW+jYE7qui+ee9Z97u+M4vh+F9eP7Fbp/XASTB+0oLIiH8F4LBDl9hHq9jlAsIxRLKKr6m1z+XiG/VeRLvkC1bhxU2KNvlMpE4gmqhoHZtujYto+PjCe4En7K4M0Ydx699rFqrcbgrQku3J7k7I0J4jNLPm47+y2nc5ucufuA09fuM/o2zfXkIgv5EnYX7kWTDAzFODc0yZPkHJ2uww/Z4PJwlPPDU1wMx0hnVve6cxwvocPo9Dynxp5zYuwVJ0emeJlZw2pb2C5sbpd5+CzJwNVxZpc+4DouVsflcy5P5HGCS+Eoklg5LIqiKsxlvxF5t8zs+oZ/2e12fbE861htXkzP02iaPQn8VZIVFj+tHxIyVNrZIZ5IsLyywtrqR96kUqRm3lORZF+QQmELQdhGFEVyQoVdueonyO7qZAoyC1mBr6VAha1WC1XTUDQNSdUQFQVJUfY+2bZpNBrouo7RNDHMlu8esWZaiHodSavR/Nnqn0P+ed7+Fh8KDvH/eK/CX8IhzNN5sIQgAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 06 21 cover" title="2024 06 21 cover" src="/static/02f729724eee309d0e698546710c2397/a331c/2024-06-21-cover.png" srcset="/static/02f729724eee309d0e698546710c2397/36ca5/2024-06-21-cover.png 200w, /static/02f729724eee309d0e698546710c2397/a3397/2024-06-21-cover.png 400w, /static/02f729724eee309d0e698546710c2397/a331c/2024-06-21-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Developer experience is a phrase repeated in multiple ways. In our industry, we finally realised how important it is to reduce the cognitive load.</strong> As our profession became mainstream, we realised that hacked mode doesn’t scale. As with lean manufacturing, we should cut waste. Waste can mean repetitive tasks that distract us from the work we do.</p> <p>Of course, I’m not saying we should try to get “in the zone”. Or we should spend hours configuring Vim keybindings and learning all <em>grep</em> params by heart before we can code. No, I don’t believe in <em>10x developers</em>. What I mean is cutting the annoying papercuts.</p> <p><strong>Today, I’d like to show you a few tricks on configuring the PostgreSQL local developer environment using Docker Compose.</strong></p> <p>Let’s start with the basic setup:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">postgres</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine <span class="token key atrule">container_name</span><span class="token punctuation">:</span> postgres <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> POSTGRES_DB=postgres <span class="token punctuation">-</span> POSTGRES_USER=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=Password12<span class="token tag">!</span> <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span></code></pre></div> <p>The most basic setup, besides adding two additional configurations through environment variables:</p> <ul> <li><em>POSTGRES_DB</em> - instructs PostgreSQL container to automatically create a default database with the provided name,</li> <li><em>POSTGRES_USER</em> - sets the default username.</li> <li><em>POSTGRES_PASSWORD</em> - sets the custom password for the PostgreSQL user.</li> </ul> <p>We could skip those variables if we’d like to use the default one, but let’s make it a bit more spicy in preparing for what comes next.</p> <p>If we run:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">docker-compose</span> up</code></pre></div> <p>Then, a new PostgreSQL container will be created and available on the <em>localhost:5432</em> (as we also exposed this port to the host). We can now try to connect our application.</p> <p>That’s nice, but what if we’d like to set up a basic database structure, such as a predefined set of tables, indexes, data, etc.? A Default Postgres image also helps here—or actually a Docker convention supported by it.</p> <p><strong>Most relational databases support a special <em>docker-entrypoint-initdb.d</em> folder.</strong> This folder is used to initialise the database automatically when the container is first created. You can put <em>.sql</em> or <em>.sh</em> scripts there, and Docker will automatically. This happens only the first time the container is started, not on subsequent restarts.</p> <p>Let’s try that and add a basic script called <em>001-init.sql</em>:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">BEGIN</span><span class="token punctuation">;</span> <span class="token comment">-- structure setup</span> <span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> users <span class="token punctuation">(</span> id <span class="token keyword">SERIAL</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> username <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> email <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">-- data setup</span> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> users <span class="token punctuation">(</span>username<span class="token punctuation">,</span> email<span class="token punctuation">)</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token string">'user1'</span><span class="token punctuation">,</span> <span class="token string">'[email protected]'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> users <span class="token punctuation">(</span>username<span class="token punctuation">,</span> email<span class="token punctuation">)</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token string">'user2'</span><span class="token punctuation">,</span> <span class="token string">'[email protected]'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">COMMIT</span><span class="token punctuation">;</span></code></pre></div> <p>It’s a simple script that shows that we can run the script transactionally (see <em>BEGIN</em> and <em>COMMIT</em>). We can also set up a database structure and insert some data.</p> <p>We could split the script into two files: <em>001-init-structure.sql</em> and <em>002-init-data.sql</em>. They will be run in alphabetical order. As you can see, you could even put the sequence of the migration scripts exported from your application there.</p> <p><strong>Cool, but how do you put it inside the container? We can use <a href="https://docs.docker.com/storage/volumes/">volumes</a> for that.</strong> Pardon the self-quote, but I wrote in my <a href="/en/tricks_on_how_to_set_up_related_docker_images/">my other article</a> that:</p> <blockquote> <p>Volumes enable storing the container data. You can restart the container, and the data will remains. It also allows to mount/bind the host operating system files to the container. It can go both ways, you can send files to the container, but you can also see generated files from the container in the host storage.</p> </blockquote> <p>How to do it? Simple as that:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">postgres</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine <span class="token key atrule">container_name</span><span class="token punctuation">:</span> postgres <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> POSTGRES_DB=postgres <span class="token punctuation">-</span> POSTGRES_USER=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=Password12<span class="token tag">!</span> <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span> <span class="token comment"># VOLUMES CONFIG</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ./docker/postgres<span class="token punctuation">:</span>/docker<span class="token punctuation">-</span>entrypoint<span class="token punctuation">-</span>initdb.d</code></pre></div> <p>Where <em>./docker/postgres</em> is a local folder path relative to the Docker Compose file. We can put our init files there, and they will be automatically copied to the Docker container during the build and then run on the first run of the Docker container instance. It’s pretty simple and helpful, isn’t it?</p> <p>Ok, let’s add more spice and show the example of the shell script run on database initialisation. Why would we do it?</p> <p>As you see, the existing PostgreSQL database container configuration is flexible. But each flexibility has its limits. <strong>What if we’d like to set up multiple databases instead of one?</strong> The existing setup won’t help. We need to do it on our own. As with any other database, <a href="https://www.postgresql.org/docs/current/app-psql.html">PostgreSQL has its command line</a>. It could be used to set up databases, run SQL scripts, etc.</p> <p>We could add a new script called <em>000-create-multiple-postgresql-databases.sh</em> and put there the following script:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token shebang important">#!/bin/bash</span> <span class="token builtin class-name">set</span> -e <span class="token builtin class-name">set</span> -u <span class="token keyword">function</span> <span class="token function-name function">create_database</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin class-name">local</span> <span class="token assign-left variable">database</span><span class="token operator">=</span><span class="token variable">$1</span> <span class="token builtin class-name">echo</span> <span class="token string">" Creating Database '<span class="token variable">$database</span>' for '<span class="token variable">$POSTGRES_USER</span>'"</span> psql -v <span class="token assign-left variable">ON_ERROR_STOP</span><span class="token operator">=</span><span class="token number">1</span> --username <span class="token string">"<span class="token variable">$POSTGRES_USER</span>"</span> <span class="token operator">&lt;&lt;-</span><span class="token string">EOSQL CREATE DATABASE <span class="token variable">$database</span>; GRANT ALL PRIVILEGES ON DATABASE <span class="token variable">$database</span> TO <span class="token variable">$POSTGRES_USER</span>; EOSQL</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">[</span> -n <span class="token string">"<span class="token variable">$POSTGRES_MULTIPLE_DATABASES</span>"</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token builtin class-name">echo</span> <span class="token string">"Multiple database creation requested: <span class="token variable">$POSTGRES_MULTIPLE_DATABASES</span>"</span> <span class="token keyword">for</span> <span class="token for-or-select variable">db</span> <span class="token keyword">in</span> <span class="token variable"><span class="token variable">$(</span><span class="token builtin class-name">echo</span> $POSTGRES_MULTIPLE_DATABASES <span class="token operator">|</span> <span class="token function">tr</span> <span class="token string">','</span> <span class="token string">' '</span><span class="token variable">)</span></span><span class="token punctuation">;</span> <span class="token keyword">do</span> create_database <span class="token variable">$db</span> <span class="token keyword">done</span> <span class="token builtin class-name">echo</span> <span class="token string">"Multiple databases created"</span> <span class="token keyword">fi</span></code></pre></div> <p>It checks if there’s a <em>POSTGRES_MULTIPLE_DATABASES</em> environment variable. If there is, it gets database names by splitting the value by a comma. Then, it runs the <em>create_database</em> function, which creates a new database and grants all permissions to the user provided through the <em>POSTGRES_USER</em> environment variable.</p> <p>Now, if we put this file into the <em>./docker/postgres</em> folder, it will be automatically run before our scripts. This will come true because we mapped this folder to the volume, and the scripts are run alphabetically.</p> <p>We need to change the of <em>POSTGRES_DB</em> into <em>POSTGRES_MULTIPLE_DATABASES</em>:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">postgres</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine <span class="token key atrule">container_name</span><span class="token punctuation">:</span> postgres <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token comment"># UPDATED TO MULTIPLE DATABASES</span> <span class="token punctuation">-</span> POSTGRES_MULTIPLE_DATABASES="postgres<span class="token punctuation">,</span>blogs<span class="token punctuation">,</span>auth" <span class="token punctuation">-</span> POSTGRES_USER=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=Password12<span class="token tag">!</span> <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ./docker/postgres<span class="token punctuation">:</span>/docker<span class="token punctuation">-</span>entrypoint<span class="token punctuation">-</span>initdb.d</code></pre></div> <p>Now, besides the default <em>postgres</em> database, two more will be created <em>blogs</em> and <em>auth</em>. You can be more creative here and use the shell scripts to customise even more database setup.</p> <p>So we have a fully set-up database and can connect to it from our application, but wouldn’t it be good to have an IDE to view data?</p> <p><strong>PostgreSQL has a decent open-source web IDE called <a href="https://www.pgadmin.org/">pgAdmin</a>.</strong> It’s possible to use it as a Docker image. Let’s to it by extending our configuration!</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">postgres</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine <span class="token key atrule">container_name</span><span class="token punctuation">:</span> postgres <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> POSTGRES_MULTIPLE_DATABASES="postgres<span class="token punctuation">,</span>blogs<span class="token punctuation">,</span>auth" <span class="token punctuation">-</span> POSTGRES_USER=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=Password12<span class="token tag">!</span> <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ./docker/postgres<span class="token punctuation">:</span>/docker<span class="token punctuation">-</span>entrypoint<span class="token punctuation">-</span>initdb.d <span class="token key atrule">pgadmin</span><span class="token punctuation">:</span> <span class="token key atrule">container_name</span><span class="token punctuation">:</span> pgadmin_container <span class="token key atrule">image</span><span class="token punctuation">:</span> dpage/pgadmin4 <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> PGADMIN_DEFAULT_EMAIL=$<span class="token punctuation">{</span>PGADMIN_DEFAULT_EMAIL<span class="token punctuation">:</span><span class="token punctuation">-</span>[email protected]<span class="token punctuation">}</span> <span class="token punctuation">-</span> PGADMIN_DEFAULT_PASSWORD=$<span class="token punctuation">{</span>PGADMIN_DEFAULT_PASSWORD<span class="token punctuation">:</span><span class="token punctuation">-</span>postgres<span class="token punctuation">}</span> <span class="token punctuation">-</span> PGADMIN_CONFIG_SERVER_MODE=False <span class="token punctuation">-</span> PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"${PGADMIN_PORT:-5050}:80"</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> postgres</code></pre></div> <p>Let’s discuss those a bit cryptic environment variables setup.</p> <ul> <li><em>PGADMIN_DEFAULT_EMAIL</em> and <em>PGADMIN_DEFAULT_PASSWORD</em> - Sets the default credentials for the pgAdmin user. pgAdmin can also be hosted as a regular service (e.g. on a test environment) and have a more advanced user setup, but a single user for local development is more than enough.</li> <li><em>PGADMIN_CONFIG_SERVER_MODE</em> - determines whether pgAdmin runs in server mode (multi-user) or desktop mode (single-user). We’re setting it to false, so we won’t be prompted for login credentials. This is an annoying papercut we’re removing.</li> <li><em>PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED</em> - controls whether a master password is required to access saved server definitions and other sensitive information. By setting this to false, we skip the additional layer of password protection for server details in pgAdmin.</li> </ul> <p>As you can see, we’re cutting security corners by just configuring the local development environment.</p> <p><strong>You may have noticed a weird syntax: <em>${PGADMIN_DEFAULT_EMAIL:<a href="mailto:[email protected]">[email protected]</a>}</em></strong>. This setup means that if <em>PGADMIN_DEFAULT_EMAIL</em> is defined in the host environment, then its value is used. Otherwise, it will fall back to the default value (in our case <em><a href="mailto:[email protected]">[email protected]</a></em>).</p> <p>You could pass such variables in the shell:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token builtin class-name">export</span> <span class="token assign-left variable">PGADMIN_DEFAULT_EMAIL</span><span class="token operator">=</span>[email protected] <span class="token builtin class-name">export</span> <span class="token assign-left variable">PGADMIN_DEFAULT_PASSWORD</span><span class="token operator">=</span>securepassword <span class="token builtin class-name">export</span> <span class="token assign-left variable">PGADMIN_PORT</span><span class="token operator">=</span><span class="token number">6666</span> <span class="token function">docker-compose</span> up</code></pre></div> <p>Or define the <em>.env</em> file in the same folder as our <em>docker-compose.yml</em> file, and Docker will use it automatically.</p> <div class="gatsby-highlight" data-language="env"><pre class="language-env"><code class="language-env">[email protected] PGADMIN_DEFAULT_PASSWORD=securepassword PGADMIN_PORT=6666</code></pre></div> <p>It’s pretty useful for security and changing variables without modifying the main script. Check also <a href="https://event-driven.io/en/docker_compose_profiles/">Docker Compose Profiles, one the most useful and underrated features</a> to learn more about running the same file with multiple variations.</p> <p>Getting back to our Docker Compose configuration. If we start containers now, we’ll also get the pgAdmin running on <em><a href="http://localhost:5050">http://localhost:5050</a></em>. We’ll be automatically logged in, but…</p> <p><strong>…but we won’t see any database automatically.</strong> How come?!</p> <p>pgAdmin doesn’t do any automatic discovery. We could setup the connection manually, but then we’d need to repeat it each time we clean up our volumes. And that happens often if we’d like to clean our test data in a fresh environment (you can do it by running <em>docker compose down -v</em>).</p> <p>Let’s change that and set up our server list automatically. Let’s start by defining the <em>servers.json</em> file inside the new <em>docker/pgAdmin</em> folder. And put there:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"Servers"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"1"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"Group"</span><span class="token operator">:</span> <span class="token string">"Servers"</span><span class="token punctuation">,</span> <span class="token property">"Name"</span><span class="token operator">:</span> <span class="token string">"Docker"</span><span class="token punctuation">,</span> <span class="token property">"Host"</span><span class="token operator">:</span> <span class="token string">"postgres"</span><span class="token punctuation">,</span> <span class="token property">"Port"</span><span class="token operator">:</span> <span class="token number">5432</span><span class="token punctuation">,</span> <span class="token property">"MaintenanceDB"</span><span class="token operator">:</span> <span class="token string">"postgres"</span><span class="token punctuation">,</span> <span class="token property">"Username"</span><span class="token operator">:</span> <span class="token string">"postgres"</span><span class="token punctuation">,</span> " <span class="token property">"Password"</span><span class="token operator">:</span> <span class="token string">"Password12!"</span><span class="token punctuation">,</span> <span class="token property">"SSLMode"</span><span class="token operator">:</span> <span class="token string">"prefer"</span><span class="token punctuation">,</span> <span class="token property">"Favorite"</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, we’re just configuring our database. We could define even more if we’d like to by adding <em>“2”: { }”</em> etc.</p> <p>If you’re generating password randomly (e.g. in the CI/CD) then you can also define passfile and replace <em>“Password”: “Password12!“,</em> with <em>“PassFile”: “/pgpass”,</em>. Then you can keep the server setup intact, but just define the database password inside the <em>passfile</em>. We could put it in the <em>docker/pgAdmin</em> folder and put it inside:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml">postgres<span class="token punctuation">:</span>5432<span class="token punctuation">:</span><span class="token important">*:postgres:Password12</span><span class="token tag">!</span></code></pre></div> <p>Let’s go on this longer path to show the full setup. We need to adjust a bit our config:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">postgres</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine <span class="token key atrule">container_name</span><span class="token punctuation">:</span> postgres <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> POSTGRES_MULTIPLE_DATABASES="postgres<span class="token punctuation">,</span>blogs<span class="token punctuation">,</span>auth" <span class="token punctuation">-</span> POSTGRES_USER=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=Password12<span class="token tag">!</span> <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ./docker/postgres<span class="token punctuation">:</span>/docker<span class="token punctuation">-</span>entrypoint<span class="token punctuation">-</span>initdb.d <span class="token key atrule">pgadmin</span><span class="token punctuation">:</span> <span class="token key atrule">container_name</span><span class="token punctuation">:</span> pgadmin_container <span class="token key atrule">image</span><span class="token punctuation">:</span> dpage/pgadmin4 <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> PGADMIN_DEFAULT_EMAIL=$<span class="token punctuation">{</span>PGADMIN_DEFAULT_EMAIL<span class="token punctuation">:</span><span class="token punctuation">-</span>[email protected]<span class="token punctuation">}</span> <span class="token punctuation">-</span> PGADMIN_DEFAULT_PASSWORD=$<span class="token punctuation">{</span>PGADMIN_DEFAULT_PASSWORD<span class="token punctuation">:</span><span class="token punctuation">-</span>postgres<span class="token punctuation">}</span> <span class="token punctuation">-</span> PGADMIN_CONFIG_SERVER_MODE=False <span class="token punctuation">-</span> PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"${PGADMIN_PORT:-5050}:80"</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> postgres <span class="token key atrule">user</span><span class="token punctuation">:</span> root <span class="token key atrule">entrypoint</span><span class="token punctuation">:</span> /bin/sh <span class="token punctuation">-</span>c "chmod 600 /pgpass; /entrypoint.sh;" <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ./docker/pgAdmin/pgpass<span class="token punctuation">:</span>/pgpass <span class="token punctuation">-</span> ./docker/pgAdmin/servers.json<span class="token punctuation">:</span>/pgadmin4/servers.json</code></pre></div> <p>We defined that our user needs to be root, as we need to be able to set up the proper permissions to the <em>passfile</em>. We can do such a setup by changing the entrypoint:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml">/bin/sh <span class="token punctuation">-</span>c "chmod 600 /pgpass; /entrypoint.sh;"</code></pre></div> <p><a href="https://docs.docker.com/reference/dockerfile/#entrypoint">Entrypoint</a> is used to specify the command executed when the container is started. Here, we’re saying that before running the regular start-up command, run an additional shell script to set proper <em>pgpass</em> permissions using the <a href="https://en.wikipedia.org/wiki/Chmod">chmod</a> function.</p> <p>We’re also mapping volumes to place <em>pgpass</em> and <em>servers.json</em> into proper folders. As you see, we can also map specific files instead of just directories.</p> <p>Now, if we run the:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml">docker compose up</code></pre></div> <p><strong>We should get pgAdmin with a preconfigured database. Sweet!</strong></p> <p>Some can say: <em>“Boy that’s a lot of plumbing!”</em>. One can be correct here, but this is the setup you do once and don’t care about. You can put this code inside the project repository and call it a day.</p> <p>Yet, there’s another option. If you’d prefer to keep it simpler for others to use and customise to your project conventions, you can build your own pgAdmin image embedding the setup.</p> <p>You could add Dockerfile with the following config (e.g. inside <em>docker/pgAdmin</em> folder):</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token comment"># Use the official pgAdmin image as the base</span> <span class="token instruction"><span class="token keyword">FROM</span> dpage/pgadmin4</span> <span class="token comment"># Set environment variables</span> <span class="token instruction"><span class="token keyword">ENV</span> PGADMIN_DEFAULT_EMAIL=<span class="token variable">${PGADMIN_DEFAULT_EMAIL:[email protected]}</span></span> <span class="token instruction"><span class="token keyword">ENV</span> PGADMIN_DEFAULT_PASSWORD=<span class="token variable">${PGADMIN_DEFAULT_PASSWORD:-postgres}</span></span> <span class="token instruction"><span class="token keyword">ENV</span> PGADMIN_CONFIG_SERVER_MODE=False</span> <span class="token instruction"><span class="token keyword">ENV</span> PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False</span> <span class="token comment"># Copy custom configuration files</span> <span class="token instruction"><span class="token keyword">COPY</span> pgpass /pgpass</span> <span class="token instruction"><span class="token keyword">COPY</span> servers.json /pgadmin4/servers.json</span> <span class="token comment"># Ensure the pgpass file has the correct permissions</span> <span class="token instruction"><span class="token keyword">RUN</span> chmod 600 /pgpass</span> <span class="token comment"># Use root user to allow necessary permissions (as in your original setup)</span> <span class="token instruction"><span class="token keyword">USER</span> root</span> <span class="token comment"># Set the entrypoint to the original entrypoint script</span> <span class="token instruction"><span class="token keyword">ENTRYPOINT</span> [<span class="token string">"/entrypoint.sh"</span>]</span> <span class="token comment"># Expose the default port (80 inside the container)</span> <span class="token instruction"><span class="token keyword">EXPOSE</span> 80</span></code></pre></div> <p>Then you can build it publish it to your container repository, and use it as:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml">```yml <span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">postgres</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine <span class="token key atrule">container_name</span><span class="token punctuation">:</span> postgres <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> POSTGRES_MULTIPLE_DATABASES="postgres<span class="token punctuation">,</span>blogs<span class="token punctuation">,</span>auth" <span class="token punctuation">-</span> POSTGRES_USER=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=Password12<span class="token tag">!</span> <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ./docker/postgres<span class="token punctuation">:</span>/docker<span class="token punctuation">-</span>entrypoint<span class="token punctuation">-</span>initdb.d <span class="token key atrule">pgadmin</span><span class="token punctuation">:</span> <span class="token key atrule">container_name</span><span class="token punctuation">:</span> pgadmin_container <span class="token key atrule">image</span><span class="token punctuation">:</span> event_driven_io_pgadmin<span class="token punctuation">:</span>latest <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"${PGADMIN_PORT:-5050}:80"</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> postgres</code></pre></div> <p>Read more in:</p> <ul> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> </ul> <p><strong>Of course, there’s a tradeoff that you then need to keep up to date, which has its costs.</strong> Typically, I’d not recommend doing it, but it can make sense for tools like pgAdmin that don’t change a lot. The choice is yours!</p> <p>I hope that this article shows you how and why tweaking the default Docker setup can be useful and how that can cut those annoying papercuts, improving the overall developer experience.</p> <p>If you get to this place, then you may also like my other articles around Docker and Continuous Integration:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/docker_compose_profiles">Docker Compose Profiles, one the most useful and underrated features</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/setting_up_nginx_with_aspnet/">Setting up NGINX load balancer for .NET WebApi</a></li> <li><a href="/en/marten_and_docker">How to create a Docker image for the Marten application</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p><strong>Also feel free to <a href="mailto:[email protected]">contact me!</a> if you think that I could help your project. I’m open on doing consultancy and mentoring to help you speed up and streghten your systems.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Setting up NGINX load balancer for .NET WebApi]]>https://event-driven.io/en/setting_up_nginx_with_aspnet/https://event-driven.io/en/setting_up_nginx_with_aspnet/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f8fac1bb08e9d2993a8f45c08b4eff19/a331c/2024-06-16-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4fAAAuHwF47oFfAAADGElEQVQoz03MWUwUZwDA8XmyTyY2qZZ4xHhfQKsggpYEiJgtFpGiSY1BjBoMkYJIYpsY72NRkapZS2kFpIAlIoqC4IoHWJZl9mDnnm9mvvnmnl13ZxUffPHJ4JMv/7ffHyNZFelJxXgra46sOVBNSEpcQDFNm1bVdwBGBTkGYJSXop9rA8kWoSVIJi8amGZNIz05w76QsuIMhn3D4QkZJWYAjAJoAWjxkslJJsFrJNAZXsMUw5F1RzUcVXeQFpeUN0C2dfN9Y8+f/3j7oomPrKDzkslLJisaSIm1P8X3e/pPdL+keXUGa2aSl2OEaJPQBrLNS4blfBjuHbxR8yuJB2U9ISFbRFEgmQK0uu89bmi69fftjmCIxnTrrWY4TX2jFZ7+m4OTVtRBepwBSs+532rSFl6uq+9/9GzMT/p9k/+/xp96X3mHvP5Xo5OvfQQBsAjBQ2R29zxsbuno+vdeIECO+8N3u++3uS9VrF3a0ujBQyxFgkCQYShxdMxP0aIADI5DDCViHMWH8KnnQ8/HX47TBC8AxDKCZiaosRcHs5a3/tEUj09DAZG0HIjACV+ApUWWFmkKkASH2XrU1G0EVZpkcX8Q9wfJCC0BFAgGL11zd3bdVWSdIliO4kZGxhuu3Tj2e9WufduvXr8i8ghLxpIqVFVJ0WU9bsYt1ZY4CGgwPDDY1tL8X2dXGA+F/AGGYC5ePpnl2lxWvn1vRVHpL66GxjMYHY4YimapugyEvr7O5r/cdzpuPhm4T09FOIKVeQj5mZfACu13Ws9fOH7qwulTZ+sqq36sPFKI1VTuxScmmEjY43HnF32/JG3esvT5m/LWjHiHWGqqtf1qb29HCPchIAz093bddh89VrZ1R3aea3Xx7hVY6Y6C2ur9R49Xbdn6XfHPeXvKS7bkp+47sPPhg7bKate6Dd9s2LSwuHRjdW15buHq5anzcgqWFhStSsvZmJazCKutO1zmyq6vP1S8K3d3+Q8le9Zv25laULIuNXN+ekZKZtbi9PWLv547+6s5s5as/TY9M6XQtSA3P2VlxqKMn7I/AZPOCmfFSAFMAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 06 16 cover" title="2024 06 16 cover" src="/static/f8fac1bb08e9d2993a8f45c08b4eff19/a331c/2024-06-16-cover.png" srcset="/static/f8fac1bb08e9d2993a8f45c08b4eff19/36ca5/2024-06-16-cover.png 200w, /static/f8fac1bb08e9d2993a8f45c08b4eff19/a3397/2024-06-16-cover.png 400w, /static/f8fac1bb08e9d2993a8f45c08b4eff19/a331c/2024-06-16-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><em>“Just put the load balancer in front of it, and call it a day”.</em></strong> But is it really that simple? Was it ever “just do XYZ?“. I was preparing a new <a href="/en/training/">workshop</a> recently. I wanted to show how to load balance Marten Async Daemon - essentially, I wanted to expand the general explanation from my <a href="/en/scaling_out_marten/">previous article on scaling out Marten</a>. And of course, the 5-minute task of work appeared to be a bit longer.</p> <p>As I’m writing this blog, not to forget what I learned, so here it is: guidance on how to configure load balancing of ASP.NET Web Api using Nginx and Docker Compose.</p> <p><strong>Let’s say we have an ASP.NET WebApi running as a Docker container.</strong> We’d like to run multiple instances of the same service to distribute the load evenly. The configuration made with the Docker Compose file could look as follows:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3.8"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> backend<span class="token punctuation">:</span> build<span class="token punctuation">:</span> dockerfile<span class="token punctuation">:</span> Dockerfile context<span class="token punctuation">:</span> . args<span class="token punctuation">:</span> project_name<span class="token punctuation">:</span> Helpdesk.Api run_codegen<span class="token punctuation">:</span> <span class="token boolean important">true</span> deploy<span class="token punctuation">:</span> replicas<span class="token punctuation">:</span> <span class="token number">3</span> depends_on<span class="token punctuation">:</span> postgres<span class="token punctuation">:</span> condition<span class="token punctuation">:</span> service_healthy restart<span class="token punctuation">:</span> always postgres<span class="token punctuation">:</span> image<span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine container_name<span class="token punctuation">:</span> postgres healthcheck<span class="token punctuation">:</span> test<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"CMD-SHELL"</span><span class="token punctuation">,</span> <span class="token string">"pg_isready -U postgres"</span><span class="token punctuation">]</span> interval<span class="token punctuation">:</span> 5s timeout<span class="token punctuation">:</span> 5s retries<span class="token punctuation">:</span> <span class="token number">5</span> environment<span class="token punctuation">:</span> <span class="token punctuation">-</span> POSTGRES_DB=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=Password12<span class="token tag">!</span> ports<span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span></code></pre></div> <p>It’s pretty standard for current applications. We have a WebApi service and related databases. In our case, that’s Postgres, as we’re running Marten as our storage library.</p> <p><strong>The most critical and non-standard setting the number of replicas:</strong></p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">deploy</span><span class="token punctuation">:</span> replicas<span class="token punctuation">:</span> <span class="token number">3</span></code></pre></div> <p>Docker Compose allows us to define using the following syntax declaratively how many instances of the defined service we’d like to run. If we now run:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">docker</span> compose up -d</code></pre></div> <p>And then</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">docker</span> <span class="token function">ps</span></code></pre></div> <p>We should see the four docker containers: 3 with WebAPI and one with PostgreSQL.</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell">CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d9f8e7410a84 helpdeskapi-backend <span class="token string">"/bin/sh -c 'dotnet …"</span> <span class="token number">14</span> minutes ago Up <span class="token number">1</span> minute <span class="token punctuation">(</span>healthy<span class="token punctuation">)</span> helpdeskapi-backend-2 399d8643bccd helpdeskapi-backend <span class="token string">"/bin/sh -c 'dotnet …"</span> <span class="token number">14</span> minutes ago Up <span class="token number">1</span> minute <span class="token punctuation">(</span>healthy<span class="token punctuation">)</span> helpdeskapi-backend-1 90492d1c32bd helpdeskapi-backend <span class="token string">"/bin/sh -c 'dotnet …"</span> <span class="token number">14</span> minutes ago Up <span class="token number">1</span> minute <span class="token punctuation">(</span>healthy<span class="token punctuation">)</span> helpdeskapi-backend-3 54327a5a155f postgres:15.1-alpine <span class="token string">"docker-entrypoint.s…"</span> <span class="token number">14</span> minutes ago Up <span class="token number">1</span> minute <span class="token punctuation">(</span>healthy<span class="token punctuation">)</span> <span class="token number">0.0</span>.0.0:5432-<span class="token operator">></span><span class="token number">5432</span>/tcp postgres</code></pre></div> <p>It’s essential to note here that we set the explicit name of the Postgres container in Docker Compose by setting <em>container_name</em>. That makes diagnostics easier, as we did above. Yet, we cannot do it for the WebApi service, as we’ll have more than one instance of the same service.</p> <p>Now, all of our WebApi services will get different IP addresses but the same DNS name. It’ll be the name of the service, so <em>backend</em>. Of course, this DNS name will be only available if we’re inside the Docker internal networking (so between containers, but not in our local/host network). To make them accessible outside and load-balance the traffic, we need a dedicated service that’ll handle that.</p> <p>We’ll use Nginx, which is one of the most popular tools. It can be used both as a Reverse Proxy and a Load Balancer.</p> <p>Small reminder:</p> <ul> <li><strong>A reverse proxy is a server that sits between client devices and the backend servers.</strong> Its main roles are to forward client requests to appropriate backend servers and send the responses back to the clients.</li> <li><strong>Load balancing is the process of distributing incoming network traffic across multiple servers.</strong> This is crucial for maintaining performance and availability, especially for high-traffic applications.</li> </ul> <p>We’ll start by extending our Docker Compose config with an additional service:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3.8"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token comment"># This is what we added</span> nginx<span class="token punctuation">:</span> restart<span class="token punctuation">:</span> always image<span class="token punctuation">:</span> nginx<span class="token punctuation">:</span>alpine ports<span class="token punctuation">:</span> <span class="token punctuation">-</span> 8080<span class="token punctuation">:</span><span class="token number">80</span> volumes<span class="token punctuation">:</span> <span class="token punctuation">-</span> ./nginx.conf<span class="token punctuation">:</span>/etc/nginx/nginx.conf depends_on<span class="token punctuation">:</span> <span class="token punctuation">-</span> backend backend<span class="token punctuation">:</span> build<span class="token punctuation">:</span> dockerfile<span class="token punctuation">:</span> Dockerfile context<span class="token punctuation">:</span> . args<span class="token punctuation">:</span> project_name<span class="token punctuation">:</span> Helpdesk.Api run_codegen<span class="token punctuation">:</span> <span class="token boolean important">true</span> deploy<span class="token punctuation">:</span> replicas<span class="token punctuation">:</span> <span class="token number">3</span> depends_on<span class="token punctuation">:</span> postgres<span class="token punctuation">:</span> condition<span class="token punctuation">:</span> service_healthy restart<span class="token punctuation">:</span> always postgres<span class="token punctuation">:</span> image<span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine container_name<span class="token punctuation">:</span> postgres healthcheck<span class="token punctuation">:</span> test<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"CMD-SHELL"</span><span class="token punctuation">,</span> <span class="token string">"pg_isready -U postgres"</span><span class="token punctuation">]</span> interval<span class="token punctuation">:</span> 5s timeout<span class="token punctuation">:</span> 5s retries<span class="token punctuation">:</span> <span class="token number">5</span> environment<span class="token punctuation">:</span> <span class="token punctuation">-</span> POSTGRES_DB=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=Password12<span class="token tag">!</span> ports<span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span></code></pre></div> <p>We added a new container running our Nginx load balancer. We’re exposing its default <em>80</em> port to a different one on our host (e.g. <em>8080</em>). It’s a good idea to do this, as many web servers are using port <em>80</em>, and we’d like to avoid accidental conflicts. We also need to provide the configuration. We’re doing that by mapping the <em>nginx.conf</em> file from the same folder as our Docker Compose file into the configuration inside the Nginx container.</p> <p><strong>And yes, we need to configure the <em>nginx.conf</em> configuration file.</strong> For our ASP.NET service, it should look as follows:</p> <div class="gatsby-highlight" data-language="toml"><pre class="language-toml"><code class="language-toml">worker_processes auto; events <span class="token punctuation">{</span> worker_connections <span class="token number">1024</span>; <span class="token punctuation">}</span> http <span class="token punctuation">{</span> map $http_connection $connection_upgrade <span class="token punctuation">{</span> <span class="token string">"~*Upgrade"</span> $http_connection; default keep-alive; <span class="token punctuation">}</span> server <span class="token punctuation">{</span> listen <span class="token number">80</span>; location / <span class="token punctuation">{</span> proxy_pass http://backend:<span class="token number">5248</span>/; proxy_http_version <span class="token number">1.1</span>; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre></div> <p>Configuration is short, but there are some things to unpack here.</p> <ol> <li><strong>We’re configuring the HTTP requests redirection.</strong> Nginx can also do more, such as TCP (e.g., PostgreSQL load balancing, but that’s not what we’re here for). That’s why we setup HTTP server configuration explicitly.</li> <li><strong>We’re load-balancing all requests by setting <em>/</em> as location.</strong> Nginx is a highly customisable and advanced tool. We could define more advanced rules and patterns, but that’ll be enough for our case.</li> <li><strong>We’re forwarding all those requests to our backend by defining:</strong></li> </ol> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">proxy_pass http://backend:5248/;</code></pre></div> <ol start="4"> <li><strong>Nginx is listening on port 80.</strong> We could also put another port address here; the most important thing is to have it aligned with the port redirection in Docker Compose.</li> </ol> <p>Plus, we have a few additional configurations needed:</p> <ol> <li><strong><em>map $http_connection $connection_upgrade { … }</em></strong> This block configures how Nginx handles connections, particularly differentiating between standard HTTP connections and upgraded WebSocket connections. ASP.NET Core applications, including those exposing APIs, might use WebSockets for real-time communication (e.g., SignalR). The map directive ensures that Nginx correctly upgrades HTTP connections to WebSockets when required, facilitating features like live updates in the UI or interactive API testing. For the same reasons, we also have below <strong><em>proxy_set_header Connection $connection_upgrade</em></strong> and <strong><em>proxy_cache_bypass $http_upgrade</em></strong> to handle WebSockets correctly (forward the upgraded headers and bypass cache, as it’s not applicable to WebSockets).</li> <li><strong><em>proxy_http_version 1.1</em></strong> -  Enforces HTTP/1.1 for proxying requests. HTTP/1.1 is necessary for features like keep-alive connections and WebSockets, which can be important for efficiently managing long-lived connections and real-time features in ASP.NET applications and for the Swagger UI’s interactive elements.</li> <li><strong><em>proxy_set_header   Host $host</em></strong> -  This header maintains the original Host header from the client request. ASP.NET applications must receive the original <em>Host</em> header for routing purposes and for the Swagger UI to correctly generate API endpoint URLs matching the client’s request host. For the same reasons we also set <strong><em>proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for</em></strong> and <strong><em>proxy_set_header X-Forwarded-Proto $scheme</em></strong>. The former adds the client’s IP address to the <em>X-Forwarded-For</em> header. The latter sets the protocol (HTTP or HTTPS) the original client uses. They’re critical to enforcing the correct security and generating the correct redirections used by Swagger UI.</li> </ol> <p>We also use the default configs:</p> <ol> <li><strong><em>worker_processes auto;</em></strong> This directive specifies the number of worker processes Nginx should spawn. Setting it to auto lets Nginx automatically determine the optimal number of worker processes based on the available CPU cores. This improves the server’s performance and efficiency.</li> <li><strong><em>events { worker_connections 1024; }</em></strong> Defines the maximum number of simultaneous connections each worker process can handle. In this case, it’s set to 1024 connections per worker.</li> </ol> <p>As you see, there are a lot of settings related to the proper WebSockets configuration and request URI redirections. Those are the trickiest parts, where “just use load balancer” becomes a typical “thank you for nothing” type of advice. To make that work fully, we also need to adjust our ASP.NET configuration. We need to add:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>AspNetCore<span class="token punctuation">.</span>HttpOverrides</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Define the availability on all IPs with the defined port</span> builder<span class="token punctuation">.</span>WebHost<span class="token punctuation">.</span><span class="token function">UseUrls</span><span class="token punctuation">(</span><span class="token string">"http://*:5248"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...) other configs</span> <span class="token comment">// Header forwarding to enable Swagger in Nginx</span> builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Configure</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ForwardedHeadersOptions<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>ForwardedHeaders <span class="token operator">=</span> ForwardedHeaders<span class="token punctuation">.</span>XForwardedFor <span class="token operator">|</span> ForwardedHeaders<span class="token punctuation">.</span>XForwardedProto<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...) other configuration</span> app<span class="token punctuation">.</span><span class="token function">UseSwagger</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseSwaggerUI</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Header forwarding to enable Swagger in Nginx</span> <span class="token punctuation">.</span><span class="token function">UseForwardedHeaders</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>I’ve spent a lot of time realising that besides <em>launchSettings.json</em>:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"profiles"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"Helpdesk.Api"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"commandName"</span><span class="token operator">:</span> <span class="token string">"Project"</span><span class="token punctuation">,</span> <span class="token property">"dotnetRunMessages"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token property">"launchBrowser"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token property">"launchUrl"</span><span class="token operator">:</span> <span class="token string">"swagger/index.html"</span><span class="token punctuation">,</span> <span class="token property">"applicationUrl"</span><span class="token operator">:</span> <span class="token string">"http://localhost:5248"</span><span class="token punctuation">,</span> <span class="token property">"environmentVariables"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"ASPNETCORE_ENVIRONMENT"</span><span class="token operator">:</span> <span class="token string">"Development"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>I also need to replicate the application URL explicitly in the ASP.NET configuration. I also need to specify the wildcard in the URL instead of the <em>localhost</em> or <em>127.0.0.1</em>. They won’t work out of the box. See also the <a href="https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/linux-nginx?view=aspnetcore-8.0&#x26;tabs=linux-ubuntu">official guide in the ASP.NET documentation</a>.</p> <p><strong>In the end, once you know it, then this looks not so hard and kinda makes sense, but yeah, once you know it. Before that, it’s never “just do XYZ”.</strong></p> <p>If you get to this place, then you may also like my other articles around Docker and Continuous Integration:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/docker_compose_profiles">Docker Compose Profiles, one the most useful and underrated features</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/marten_and_docker">How to create a Docker image for the Marten application</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p><strong>Also feel free to <a href="mailto:[email protected]">contact me!</a> if you think that I could help your project. I’m open on doing consultancy and mentoring to help you speed up and streghten your systems.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Combining the To-Do List and the Passage Of Time patterns for resilient business workflows]]>https://event-driven.io/en/to_do_list_and_passage_of_time_patterns_combined/https://event-driven.io/en/to_do_list_and_passage_of_time_patterns_combined/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ce5f962c100572ddf5d1cfdde678bee9/a331c/2024-06-07-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4gAAAuIAHVHB4bAAACA0lEQVQoz2NgQAKsYMDAwMDIyAghWVlZWVhYWOEAxGSByKKDmJjY+vqG9ra26urq3p7uluam+obGpKTkuLiEzMyszMyspqbmvLx8AQEBuAUIwMXFxcDAuGjBjFs3rx4/f7usOJ+BgYGXBwQYGBj8AwJmzJhZWVklwI9NMxMTEwMDg5a6yvFjxw9dfN5bnMEAVgUiGBgFBQTFxMSFhISwOxuuPzgkbMvmzfVFOUZ6OoICguLiEovnz+puq4crwAIgRmZlZrY01DIwMAgJi9y8fvXjpy/Pn7/4//9nV3sDAwMDMzMzTjvNzS3ev39/6vAeAT4+aWmZJ4+ffnz98vHDh/NnTVWQkwYpw3QzIxgwMDCICIt8+vL17LkLoiKiDAwMVuZm548cmj5tuqKCAicnJ5ZwguoHy4iKir559erkyVMWllY2tnb2Do5Pnr3YtGFjTHggFzc3Fm0sLCwmJqb+AUET+ifs2b7h8sXzFy5dWb9h45q165evXH3hwsXD+/csXDB/yvRZ7R1dNja2KPazsrAaGRnV11StXbN2ydJl6zdungcC8ydOnDRn7vxVq1Zv2rxl2fKVJSUlc+bMrayqRgkziDFaWpp9ff0lpWVZ2dnpaelZWdktra25uXl9/RMaGpvq6uqzsnNqa+vSUtPY2dmxRBI82ODegadkRkZGNhiAqwEAG0ewmuf0DTwAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 06 07 cover" title="2024 06 07 cover" src="/static/ce5f962c100572ddf5d1cfdde678bee9/a331c/2024-06-07-cover.png" srcset="/static/ce5f962c100572ddf5d1cfdde678bee9/36ca5/2024-06-07-cover.png 200w, /static/ce5f962c100572ddf5d1cfdde678bee9/a3397/2024-06-07-cover.png 400w, /static/ce5f962c100572ddf5d1cfdde678bee9/a331c/2024-06-07-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Managing processes is non-trivial. I have written about it in multiple posts and told you the horror story of the <a href="/en/no_it_can_never_happen/">case that should have never happened</a>.</strong> Business processes are usually the most critical part of the core functionality, so we need to ensure that we can diagnose them correctly. We also need to ensure that they won’t be stuck in the middle without being able to resume them. At war, love, and managing processes, all tricks are allowed.</p> <p>Today, I’d like to show you how combining two useful patterns can help you in that:</p> <ul> <li><a href="https://verraes.net/2019/05/patterns-for-decoupling-distsys-passage-of-time-event/">Passage of Time</a> described by Mathias Verraes</li> <li><a href="https://blog.bittacklr.be/the-to-do-list-pattern.html">To-do List</a> described by Yves Reynhout.</li> </ul> <p><strong>Let’s say we’re managing the Ordering process described in <a href="https://event-driven.io/en/saga_process_manager_distributed_transactions/">my other article</a>.</strong> We must ensure that once the client confirms the shopping cart, it’ll be paid for and shipped. Even if we design a compensation flow to cancel the payment if the product is unavailable, etc., we might not predict all potential scenarios. There may be transient errors, bugs in the code, and other cases where our flow can get stuck.</p> <p>In most similar flows, there’s always a deadline for how long the process can be ongoing. For instance, users may have 15 minutes to make a payment, or we may need to complete information about availability. We can also use this deadline for other cases, such as unpredictable ones. Thanks to that, if the order is not completed, it will be cancelled automatically. That’s not perfect, but if it happens rarely, then it’s usually good enough. The user can try to start the ordering process again, or we can take other compensating operations.</p> <p>There are many ways to do it. Today, we’ll focus on combining the mentioned patterns, as it provides a straightforward way to handle our timeout strategy.</p> <p>Our ordering process could be reflected by the following set of events:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">OrderEvent</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">OrderInitiated</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> OrderId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> TotalPrice<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> InitiatedAt<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> TimeoutAfter <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">OrderEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">OrderPaymentRecorded</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> OrderId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> PaymentId<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Amount<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> PaymentRecordedAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">OrderEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">OrderCompleted</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> OrderId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> CompletedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">OrderCancelled</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> OrderId<span class="token punctuation">,</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> PaymentId<span class="token punctuation">,</span> <span class="token class-name">OrderCancellationReason</span> OrderCancellationReason<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> CancelledAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">OrderEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">OrderEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Based on them, we could define the read model informing us about pending orders:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PendingOrder</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> TimeoutAfter <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And build it from events:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PendingOrdersProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">SingleStreamProjection<span class="token punctuation">&lt;</span>PendingOrder<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">PendingOrdersProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token generic-method"><span class="token function">DeleteEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderCompleted<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">DeleteEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderCancelled<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">PendingOrder</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">OrderInitiated</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">PendingOrder</span> details<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PendingOrder</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>TimeoutAfter<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>I’m using <a href="/en/projections_in_marten_explained/">Marten projections</a> here, but the syntax doesn’t matter much. More importantly, I’m adding new raw when Order is initiated and removing it upon order completion or cancellation. I’m also storing the information on the desired time-out. This time will be decided in the business logic that creates the <em>OrderInitiated</em> event.</p> <p><strong>The pending order read model is on a To-Do List.</strong> It keeps the information about the orders that must be completed or cancelled. I’m keeping a minimum set of information there. As completion will be handled by a different flow (positive scenario), I only need information about the potential cancellation.</p> <p>Cool, but how do you handle the cancellation? The common approach is to use scheduled commands. If our tooling lets us do it (as many messaging frameworks do), we can say: <em>“Schedule me a TimeoutOrder command in 15 minutes”</em>. That’s a viable and fine approach, but it’s not perfect. Logically, we’re scheduling an action, that in most cases should not happen, as it’s an edge case. But we’re scheduling it always, for good reasons, but just in case. That’s also pretty vulnerable to any mishaps handling them, as e.g. if it’s muted or not handled correctly then error can be swallowed and not retried. Handling the scheduled command is like plot twist in movies, when killed villian appeared to be alive and striking back. It works, but definitely there are more elegant solutions.</p> <p>The alternative is Passage of Time pattern. Mathias Verraes wrote about it as:</p> <blockquote> <p>The mind switch is to think of the passage of time as just another <em>Domain Event</em>, exactly like all the other events. After all, if we define a <em>Domain Event</em> as a granular point in time where something happened that is relevant to the business, then certainly the next business day, month, or quarter, is extremely relevant.</p> <p>In the new design, a cron or scheduler emits generic Passage of Time Events at regular intervals, such as a <em>DayHasPassed {date}</em> event at midnight, or a <em>QuarterHasPassed {year, quarter}</em>. All interested services listen for this event. They can react to it, by performing an action, by increasing a counter, or by querying some database and filter by date, to find items that have some work to be done.</p> </blockquote> <p>That’s precisely what we could do in our case. If we had an event as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">MinuteHasPassed</span><span class="token punctuation">(</span> <span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> PreviousTime <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then we could write the following handler:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HandleCancelOrder</span><span class="token punctuation">(</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">TimeProvider</span> timeProvider <span class="token punctuation">)</span><span class="token punctuation">:</span> IEventHandler<span class="token operator">&lt;</span>TimeHasPassed<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">MinuteHasPassed</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> orderIds <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Query</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>PendingOrder<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>o <span class="token operator">=></span> o<span class="token punctuation">.</span>TimeoutAfter <span class="token operator">&lt;=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Now<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>o <span class="token operator">=></span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">token</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> now <span class="token operator">=</span> timeProvider<span class="token punctuation">.</span><span class="token function">GetUtcNow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> orderId <span class="token keyword">in</span> orderIds<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">GetAndUpdate</span><span class="token punctuation">(</span> orderId<span class="token punctuation">,</span> order <span class="token operator">=></span> order<span class="token punctuation">.</span><span class="token function">Cancel</span><span class="token punctuation">(</span>OrderCancellationReason<span class="token punctuation">.</span>TimedOut<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">ct</span><span class="token punctuation">:</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We first filter pending orders that should be timed out and get their IDs. Then, we try to cancel each one with the timeout reason (note: it could also be a dedicated method if handling is different from manual cancellation).</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Order</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">public</span> <span class="token return-type class-name">OrderCancelled<span class="token punctuation">?</span></span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token class-name">OrderCancellationReason</span> cancellationReason<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> now<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>OrderStatus<span class="token punctuation">.</span>Closed<span class="token punctuation">.</span><span class="token function">HasFlag</span><span class="token punctuation">(</span>Status<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">OrderCancelled</span><span class="token punctuation">(</span> Id<span class="token punctuation">,</span> PaymentId<span class="token punctuation">,</span> cancellationReason<span class="token punctuation">,</span> now <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As a result, we’ll store the <em>OrderCancelled</em> event that will trigger removing the pending order from our To-Do List item.</p> <p><strong>What if the <em>MinuteHasPassed</em> event handling will be delayed?</strong> Typically, that’s not a big deal, as timing out doesn’t need to happen at the precise second. It’s typically good enough to happen as soon as possible after a certain time.</p> <p><strong>What if one of <em>MinuteHasPassed</em> will be lost?</strong> Then, the next one will be published in another minute. As mentioned above, it’s not a big deal.</p> <p><strong>What if cancelling one of the pending orders fails?</strong> Not a big deal, as it will be caught again by the next passage of time cadence.</p> <p><strong>What if a race condition exists between our pending order list and our order write model?</strong> Of course, it may happen that someone managed to complete an order at the very last moment. We should silently fail to cancel it, and that’s what we’re doing in business logic by returning a null instead of the event. We should always use <a href="/en/optimistic_concurrency_for_pessimistic_times/">Optimistic Concurrency</a> to ensure consistency. That’s also why we’re doing operations on the aggregate instead of just storing the <em>OrderCancelled</em> event. This should also help us if multiple instances handle the same To-Do List and compete for resources. In the worst case, the first succeeds, while the other fails silently.</p> <p>Also, we can connect multiple To-Do Lists to the same passage of time events. As we’ll be continuously triggering those events, we can do it even with simple in-memory tooling.</p> <p><strong>All of that creates a simple but powerful and resilient combination.</strong></p> <p>The example using the <a href="https://www.quartz-scheduler.net">Quartz.NET</a> library can look like this: We need to register our jobs with specific schedules (in our case, minute, hour, day).</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">QuartzExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token function">AddQuartzDefaults</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token operator">=></span> services <span class="token punctuation">.</span><span class="token function">AddQuartz</span><span class="token punctuation">(</span>q <span class="token operator">=></span> q <span class="token punctuation">.</span><span class="token function">AddPassageOfTime</span><span class="token punctuation">(</span>TimeUnit<span class="token punctuation">.</span>Minute<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddPassageOfTime</span><span class="token punctuation">(</span>TimeUnit<span class="token punctuation">.</span>Hour<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddPassageOfTime</span><span class="token punctuation">(</span>TimeUnit<span class="token punctuation">.</span>Day<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddQuartzHostedService</span><span class="token punctuation">(</span>q <span class="token operator">=></span> q<span class="token punctuation">.</span>WaitForJobsToComplete <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollectionQuartzConfigurator</span> <span class="token function">AddPassageOfTime</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollectionQuartzConfigurator</span> q<span class="token punctuation">,</span> <span class="token class-name">TimeUnit</span> timeUnit <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> jobKey <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">JobKey</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"PassageOfTimeJob_</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">timeUnit</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> q<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddJob</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>PassageOfTimeJob<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>opts <span class="token operator">=></span> opts<span class="token punctuation">.</span><span class="token function">WithIdentity</span><span class="token punctuation">(</span>jobKey<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> q<span class="token punctuation">.</span><span class="token function">AddTrigger</span><span class="token punctuation">(</span>opts <span class="token operator">=></span> opts <span class="token punctuation">.</span><span class="token function">ForJob</span><span class="token punctuation">(</span>jobKey<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">WithIdentity</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">jobKey</span><span class="token punctuation">}</span></span><span class="token string">-trigger"</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UsingJobData</span><span class="token punctuation">(</span><span class="token string">"timeUnit"</span><span class="token punctuation">,</span> timeUnit<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">WithSimpleSchedule</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span><span class="token function">WithInterval</span><span class="token punctuation">(</span>timeUnit<span class="token punctuation">.</span><span class="token function">ToTimeSpan</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> q<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Having that, we can we can define our job as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PassageOfTimeJob</span><span class="token punctuation">(</span><span class="token class-name">IEventBus</span> eventBus<span class="token punctuation">,</span> <span class="token class-name">TimeProvider</span> timeProvider<span class="token punctuation">)</span><span class="token punctuation">:</span> IJob <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">Execute</span><span class="token punctuation">(</span><span class="token class-name">IJobExecutionContext</span> context<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> timeUnit <span class="token operator">=</span> context<span class="token punctuation">.</span>MergedJobDataMap<span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token string">"timeUnit"</span><span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">.</span><span class="token function">ToTimeUnit</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> eventBus<span class="token punctuation">.</span><span class="token function">Publish</span><span class="token punctuation">(</span> timeUnit<span class="token punctuation">.</span><span class="token function">ToEvent</span><span class="token punctuation">(</span>timeProvider<span class="token punctuation">.</span><span class="token function">GetUtcNow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> context<span class="token punctuation">.</span>PreviousFireTimeUtc<span class="token punctuation">)</span><span class="token punctuation">,</span> CancellationToken<span class="token punctuation">.</span>None <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>EventBus is a simple in-memory messaging implementation that will trigger all registered handlers for our event.</p> <p>As different jobs will need different cadences, we’d like to be able to subscribe to different time passages explicitly. Because of that, we’ll define a dedicated event types:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">TimeHasPassed</span><span class="token punctuation">(</span><span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> PreviousTime<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">MinuteHasPassed</span><span class="token punctuation">(</span><span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> PreviousTime<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">TimeHasPassed</span><span class="token record-arguments"><span class="token punctuation">(</span>Now<span class="token punctuation">,</span> PreviousTime<span class="token punctuation">)</span></span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">HourHasPassed</span><span class="token punctuation">(</span><span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> PreviousTime<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">TimeHasPassed</span><span class="token record-arguments"><span class="token punctuation">(</span>Now<span class="token punctuation">,</span> PreviousTime<span class="token punctuation">)</span></span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">DayHasPassed</span><span class="token punctuation">(</span><span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> PreviousTime<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">TimeHasPassed</span><span class="token record-arguments"><span class="token punctuation">(</span>Now<span class="token punctuation">,</span> PreviousTime<span class="token punctuation">)</span></span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>You could go wild and customise that more, but I’m sure you’re getting the point.</p> <p>The final thing is to define our TimeUnit and the helper conversion methods:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">TimeUnit</span> <span class="token punctuation">{</span> Minute<span class="token punctuation">,</span> Hour<span class="token punctuation">,</span> Day <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">TimeUnitExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">TimeUnit</span> <span class="token function">ToTimeUnit</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name"><span class="token keyword">string</span></span> timeUnitString<span class="token punctuation">)</span> <span class="token operator">=></span> Enum<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Parse</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TimeUnit<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>timeUnitString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">TimeSpan</span> <span class="token function">ToTimeSpan</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">TimeUnit</span> timeUnit<span class="token punctuation">)</span> <span class="token operator">=></span> timeUnit <span class="token keyword">switch</span> <span class="token punctuation">{</span> TimeUnit<span class="token punctuation">.</span>Minute <span class="token operator">=></span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromMinutes</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>Hour <span class="token operator">=></span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromHours</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>Day <span class="token operator">=></span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromDays</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>timeUnit<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"Not expected time unit value: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">timeUnit</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">TimeHasPassed</span> <span class="token function">ToEvent</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">TimeUnit</span> timeUnit<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> now<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> previous<span class="token punctuation">)</span> <span class="token operator">=></span> timeUnit <span class="token keyword">switch</span> <span class="token punctuation">{</span> TimeUnit<span class="token punctuation">.</span>Minute <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MinuteHasPassed</span><span class="token punctuation">(</span>now<span class="token punctuation">,</span> previous<span class="token punctuation">)</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>Hour <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">HourHasPassed</span><span class="token punctuation">(</span>now<span class="token punctuation">,</span> previous<span class="token punctuation">)</span><span class="token punctuation">,</span> TimeUnit<span class="token punctuation">.</span>Day <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DayHasPassed</span><span class="token punctuation">(</span>now<span class="token punctuation">,</span> previous<span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>timeUnit<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"Not expected time unit value: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">timeUnit</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Is it always a good solution? Only Siths deal in absolutes. This can swamp your database if you have many To-Do Lists with many records. Especially if you add the passage of time in seconds or milliseconds. That’s why I made the To-Do List data so minimal and ensured that items would be cleared right after the order was completed or cancelled. If you want to create a detailed view, don’t reuse it with the To-Do List; create a dedicated read model.</p> <p>If you have many tasks that need to be scheduled quickly and tight performance requirements, scheduled messages may be a better option. But as always, don’t assume. Ask for metrics, measure, compare, and then make the final decision.</p> <p><strong>The combination of the To-Do List and the Passage of Time patterns is simple but powerful.</strong> In most systems, processes/checks need to happen every day, every hour, or every minute. This combination allows us to build a loosely coupled, scalable and resilient processing. Of course, we should not replace the classical process handling, but it should be a good fit for features like time-based deadlines or triggers.</p> <p><strong>If you liked this article and would like to expand that, I do also paid one-to-one mentoring sessions, consulting and <a href="/en/training/">group workshops</a>. Don’t hesitate to <a href="mailto:[email protected]">contact me!</a> if you’re interested!</strong></p> <p>Read also more in:</p> <ul> <li><a href="/en/saga_process_manager_distributed_transactions/">Saga and Process Manager - distributed processes in practice</a>,</li> <li><a href="/en/event_driven_distributed_processes_by_example/">Event-driven distributed processes by example</a>,</li> <li><a href="/en/how_to_have_fun_with_typescript_and_workflow/">How TypeScript can help in modelling business workflows</a>,</li> <li><a href="/en/how_to_update_past_data_in_event_sourcing/">Oops I did it again, or how to update past data in Event Sourcing</a>,</li> <li><a href="/en/event_transformations_and_loosely_coupling/">Event transformations, a tool to keep our processes loosely coupled</a>,</li> <li><a href="/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/">Testing asynchronous processes with a little help from .NET Channels</a>,</li> <li><a href="/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/">Set up OpenTelemetry with Event Sourcing and Marten</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore#105-distributed-processes">Set of recommended materials about distributed processes</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Let's build the worst Event Sourcing system!]]>https://event-driven.io/en/lets_build_the_worst_event_sourcing_system/https://event-driven.io/en/lets_build_the_worst_event_sourcing_system/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f427cc7be1ddfca8643259ccd11a478e/a331c/2024-06-01-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4gAAAuIAHVHB4bAAAC9klEQVQoz1WSSU9TUQBGn4krxIpCmUFipVZoSyGFAoo2qYImhoUmBn+AidEfIC5cGDfGIcSgUBXROKOWNhVBgWARkSKUImgwDE5xiLbvvfvu8Ob2mia68OzP4jv5GEQUTiAsQKKkKlpCUhOKnpQUncgaklQtSUVFh0RW9aSsUUWnRNawqCBRAUhkAJLiPEKiNjM73365801kdnH5U2hs/JEv8H5haWw8PBWZ9fmDE5PTL19N9D8bikTnEVEAlAQkMSyPWYCxrIenog8f+8fDU4PDoZHR1497n4yExm7ffTD6KvxsONQ3MBx8OnSt+9Z0dB5gmQMYQJHhoAiQxEMRS9rXH7G594vRt+9mo9GJyem+wReBp4PBYNDf6/P7fXduXllZWVF0GucRCzAPCMNDiYcSJxBRSbRdPGfaWtHkaWhp3uF0Vu1x7/RYN5lKy6urXQ0N9blZGQ97enRKWYA4SHjhryxygBCVdrSfr3VaPe66fY3brDZbs3t72+H9m80Vzjq3e1dTuqHw+o17SUrjAPNQTMmQyJDIApJ0Sr1er91uP7R/d42r9kCj21XpOHL0WH5evslsrXLWrTUYO7xdlFIOktRSgTAswCwgMQ6qCeq91l1isjR5GqqdTpvFbDaZqiorMzdklpZaXNt2rjFknTnblqD0NwtZQDhAGE4QOSElU0o7u+/b7LaKCkd2YVlxyZbc3IL8vIK16zJz8jY6nPXphpxTp88kKGV5DKCUqs0CEudxjINakvqD/eWO2tziLcYiq8FoLsgvzFiffXBv/T63y1JmN6wztp44qSUpCzAHCAtw6iQAyTEOKRoNDAwXWxzZWVnGzA2binLS09JWrVrtqXG0NO4ot9lLiotaj7dKGv3NQUhUIieYDx+/f/76jQVIUem7pU9n269eaLvU1XV9oM93937Po96AL9DfNxgaD0cmI3Mfv3xHoiKpicmpmedDI0ycRzEWcAAjLCuqTv+hU5qk/6EnKZY0AYmyqn/7+WthcfkPMk0TXBClZUcAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 06 01 cover" title="2024 06 01 cover" src="/static/f427cc7be1ddfca8643259ccd11a478e/a331c/2024-06-01-cover.png" srcset="/static/f427cc7be1ddfca8643259ccd11a478e/36ca5/2024-06-01-cover.png 200w, /static/f427cc7be1ddfca8643259ccd11a478e/a3397/2024-06-01-cover.png 400w, /static/f427cc7be1ddfca8643259ccd11a478e/a331c/2024-06-01-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Everyone likes to talk about best practices.</strong> I went the other way around and gathered all the worst practices on how to build the worst Event Sourcing system. Was it easy?</p> <p>It was not, as building the worst Event Sourcing system, we cannot cheat and just go with Event Streaming rebranding the name. What to do next?</p> <p>Check the recording of my talk at NDCLondon 2024 where I told the story of the project Franz: the worst Event Sourcing system.</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/20zvAJAhqS0?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>Jokes aside. Event Sourcing is perceived as a complex pattern that’s challenging to learn.</strong> In fact, it’s pretty simple, but the common misconception and the way it’s taught may lead to such a conclusion. By going through the worst ideas, I wanted to show the essence of Event Sourcing. I went through the massive set of internet articles and gathered the worst of them. Unfortunately, this wasn’t hard, as there were a lot of misconceptions spread around.</p> <p><strong>That was probably the hardest talk I gave so far from the presenting technique.</strong> I tried to be funny but not silly. The narrative has a few twists and is a bit inverted. But well, I think that not only attendees but also the speaker need to have some fun from time to time!</p> <p>If you want to know how to do it and get a quick start, then check my NDC Oslo talk:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/jnDchr5eabI?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>Or articles on this blog like <a href="/en/event_streaming_is_not_event_sourcing/">Event Streaming is not Event Sourcing!</a> where I tackled the most common misconception.</p> <p><strong>If you need more one-to-one help <a href="mailto:[email protected]">contact me!</a>, I’m open for consultancy. Check also my <a href="/en/training/">workshops page</a> for more end-to-end learning opportunity.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Why you should batch message processing and how to do it with .NET AsyncEnumerable]]>https://event-driven.io/en/batching_async_enumerable/https://event-driven.io/en/batching_async_enumerable/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/402acbaa5f6d413a6d5605649bed0cb6/a331c/2024-05-24-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4hAAAuIQEHW/z/AAADJElEQVQozwEZA+b8AFNMQUxGO0I6LzQoG0Y1JVhAK2JFLW9TPHBYRHloV2pfUVRNQa6vq9TX1sjLytLU1eLj5Ovs7fHw8PTz8wBkWElNQDFCMyNYRTRmUT1+ZlK6qJrd0MWXeWB0XEdqWkhqYlO3t7Le4eDU19XW2tnZ3N7g4+Pk5+bn6ekAZE44V0Aqak43mnJV3sWx3cu89NrG997K1L+vvqqZpZiJbWNTu7q14eXk2tzb4OLg4+Xl5Ofn5enp6OvqAJh2XKaKdntRNptnSOjHt+XIueS9p+jBqOfIuO7Ou9/DsHdrWrq6tOLl5Nzd2+Hi4OPl5eDj5eLl5enq6gDYvqjQvKx+XEaYa03fwrDezcPZtp3evKTfybzgw7HQr556bl67urbk5uTf39zh4uDh4uHh4+Ll5uXp6egAzbWjwqyagGBKlW1Q3bCJ2LWZ16yH3LKP2rib3bOQza6TfHFhwL+67evq5OTh5OXj5OXk5ebl6ejo6urqALKjk6aOe3pVOo9nStqvitWmgNOje9WphdasitmrhceXa4V4aMjGw/P09O3t7PHx8fLz9PHy8/Dx8e3s6wCjm4qdjHlxUTmIYkfeqn3VtJrUqITXr5DUuqnZr4/Gp5FgVUezsazX1dLGxMHCwL2+ura6ta+lnpWsoZQAhn9ugnNhaEoyg19E3Kl52ad71aZ+16+O16+N2ayGzqB4aGBXbGpmYVlQQDUsY15UZVpNUEMzdWtfkoh8AG9rYGxiVU4zIHZUPtGXcM6njdGkiNSlitaxoNijhsqfhpmWkairqra3tq+wrbm7ubOxrJCIf8HAvMrFvwBcXFJhWk1EJxZwSjXSjGPQk2zVnXvYpYHar43erojOnniHhH2RlZSgo6Gsr661t7XHysnc3t7g4N7o5uUAUVBJXE0+Vzsne1lAzJ92zJ9306V8zKJ90KeFzKiLzq2TuKaXiIuHl5qYoaOhoaSiqaups7SzwsPCy8zJAEhHQDMsJDstIWhSQsyvm7Gai419cauek6yglol+c42FepKLgoaIhJCTkZaZl6GjoauurLa4t7/BwLe5tr7W6pB2B/MiAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 05 24 cover" title="2024 05 24 cover" src="/static/402acbaa5f6d413a6d5605649bed0cb6/a331c/2024-05-24-cover.png" srcset="/static/402acbaa5f6d413a6d5605649bed0cb6/36ca5/2024-05-24-cover.png 200w, /static/402acbaa5f6d413a6d5605649bed0cb6/a3397/2024-05-24-cover.png 400w, /static/402acbaa5f6d413a6d5605649bed0cb6/a331c/2024-05-24-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8">AsyncEnumerable</a> is a sneaky abstraction.</strong> It allows simplified and performant usage for iterating on pull-based and push-based sources.</p> <p><em>“Pull-based and push-based sources”</em> sound smart, but what are they?</p> <p><strong>Pull-based</strong> approach is when we’re querying a specific data source, such as, running a select statement on a relational database. We pull the data from it.</p> <p><strong>Push-based</strong> is when we’re getting notifications from an external source. It can be a messaging system, <a href="/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/">event store subscription</a>, or also <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">change notifications from the database</a>. We don’t need to query for information; we’ll be notified.</p> <p>How does <em>AsyncEnumerable</em> help in both approaches? For push-based, it’s pretty obvious. We can use a familiar <em>foreach</em> statement, and the <em>AsyncEnumerable</em> implementation will internally forward the notification and wait until the new one appears. For instance, if you use <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">API for Postgres Logical Replication</a>, you can do it as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> slotName<span class="token punctuation">,</span> publicationName<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> conn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">LogicalReplicationConnection</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> slot <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationSlot</span><span class="token punctuation">(</span>slotName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> message <span class="token keyword">in</span> conn<span class="token punctuation">.</span><span class="token function">StartReplication</span><span class="token punctuation">(</span>slot<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationOptions</span><span class="token punctuation">(</span>publicationName<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>message <span class="token keyword">is</span> <span class="token class-name">InsertMessage</span> insertMessage<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> <span class="token keyword">await</span> InsertMessageHandler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>insertMessage<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> conn<span class="token punctuation">.</span><span class="token function">SetReplicationStatus</span><span class="token punctuation">(</span>message<span class="token punctuation">.</span>WalEnd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">SendStatusUpdate</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>For a push-based approach, <em>AsyncEnumerable</em> (even if it’s not asynchronous) is also useful.</strong> Let’s say you’re using the EventStoreDB API to read events from the stream. Most of the time, <a href="/en/closing_the_books_in_practice/">streams will be short</a>, as they should represent the history of a particular entity/process. Most of the entities in our system don’t have many operations happening. Still, sometimes we can have more events. Loading all of them in one batch (think: reading them with <em>ToListAsync</em>) won’t be efficient. It’ll increase the memory pressure if we load all of them at once. If we’re <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">building the state from events</a>, we don’t need to load all of them. It’s enough just to have a single one, apply it to the state, and get the next event.</p> <p>That’s also a reason why EventStoreDB Api for reading streams is returning AsyncEnumerable to give you the option to load into memory just a subset of events, reducing memory pressure. Thanks to that, you can write the efficient state aggregation as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>T<span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">AggregateStream</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">EventStoreClient</span> eventStore<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellationToken<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">ulong</span><span class="token punctuation">?</span></span> fromVersion <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">IProjection</span></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> readResult <span class="token operator">=</span> eventStore<span class="token punctuation">.</span><span class="token function">ReadStreamAsync</span><span class="token punctuation">(</span> Direction<span class="token punctuation">.</span>Forwards<span class="token punctuation">,</span> StreamNameMapper<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ToStreamId</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">,</span> fromVersion <span class="token operator">??</span> StreamPosition<span class="token punctuation">.</span>Start<span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> cancellationToken <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">await</span> readResult<span class="token punctuation">.</span>ReadState<span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span> <span class="token operator">==</span> ReadState<span class="token punctuation">.</span>StreamNotFound<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> aggregate <span class="token operator">=</span> <span class="token punctuation">(</span>T<span class="token punctuation">)</span>Activator<span class="token punctuation">.</span><span class="token function">CreateInstance</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">T</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> readResult<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> eventData <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token function">Deserialize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> aggregate<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>eventData<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> aggregate<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>That’s great, but having the API that always returns you a single event has also limitations. One of them is performance.</strong></p> <p>Let’s say we’re handling upcoming events from a messaging system or event store subscription and updating read models based on them. Trying to update them after each event will create a lot of network traffic between our system and the database. I explained in <a href="/en/projecting_from_marten_to_elasticsearch/">the other article how essential it is for a database like Elasticsearch</a>.</p> <p><strong>The potential solution is to do batching</strong>. We could gather upcoming events into a collection and run updates in batches. But that’s also tricky, as events won’t come in the unified batch sizes. It may happen that you got 5 events, then 2 events one minute later, and 100 events 5 minutes later. The notification cadence will depend highly on the traffic that creates those events. If we set our batch to have 10 events, we’d need to wait 7 minutes to process them (until the batch is fully filled). That’s not great and, in most cases, not acceptable. It could only work if our traffic is extremely stable and continuous. We need to do better than that.</p> <p><strong>To make batching efficient, we should also define the deadline for the batches.</strong> We should say: <em>“Either try to gather batch of size X or wait Y milliseconds to process it. Whetever happens first.”</em>.</p> <p>Unfortunately, AsyncEnumerable doesn’t provide us with the API for that. Let’s discuss how to fix it!</p> <p>In .NET we have <em>competing</em> API for handling asynchronous processing: <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/channels">System.Threading.Channels</a>. They’re great but a bit harder for some people to reason for. We’re no longer operative on a straightforward imperative programming model but making things reactive. (Read more on the usefulness of .NET channels in <a href="/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/">my other article</a>). If we were using .NET channels, we could use the nice contrib package <a href="https://github.com/Open-NET-Libraries/Open.ChannelExtensions">Open.ChannelExtensions</a> and do things like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> channel <span class="token operator">=</span> Channel<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">CreateUnbounded</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> batchingReader <span class="token operator">=</span> channel<span class="token punctuation">.</span>Reader <span class="token punctuation">.</span><span class="token function">Batch</span><span class="token punctuation">(</span>batchSize<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">WithTimeout</span><span class="token punctuation">(</span>deadline<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re creating a channel and reader that’d precisely do what we’d like to achieve with AsyncEnumerable, so batching by size or deadline. We could produce new data by writing to the channel as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">await</span> channel<span class="token punctuation">.</span>Writer<span class="token punctuation">.</span><span class="token function">WriteAsync</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Cool! But we want to do this with AsyncEnumerable. Maybe we could wrap this code somehow? Yes, we can!</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span>List<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Batch</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> enumerable<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> batchSize<span class="token punctuation">,</span> <span class="token class-name">TimeSpan</span> deadline<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> enumerable <span class="token punctuation">.</span><span class="token function">ToChannel</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Batch</span><span class="token punctuation">(</span>batchSize<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">WithTimeout</span><span class="token punctuation">(</span>deadline<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AsAsyncEnumerable</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Yup, simple as that! <a href="https://github.com/Open-NET-Libraries/Open.ChannelExtensions">Open.ChannelExtensions</a> provides an API to forward AsyncEnumerable to the .NET channel and vice versa. Thanks to that, we’ve built a reusable method for use in any AsyncEnumerable. We could use it in the presented above logical replication, or for instance <a href="/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/">event store subscription</a>:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> subscription <span class="token operator">=</span> eventStoreClient <span class="token punctuation">.</span><span class="token function">SubscribeToAll</span><span class="token punctuation">(</span>FromAll<span class="token punctuation">.</span>Start<span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Batch</span><span class="token punctuation">(</span><span class="token number">100</span><span class="token punctuation">,</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromMilliseconds</span><span class="token punctuation">(</span><span class="token number">150</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> events <span class="token keyword">in</span> subscription<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">HandleBatch</span><span class="token punctuation">(</span>events<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Of course, setting a proper batch size and deadline is not easy and is highly contextual to your use case. That’s something that should be fine-tuned or even made adaptive.</p> <p>As you can see, it’s not that scary, and the code is simple. But all is simple if you’re aware of the issue and already know how to solve it. I hope that this code will help you to make your async code more efficient and performant.</p> <p><strong>If you need more help <a href="mailto:[email protected]">contact me!</a>, I’m open for consultancy. Check also my <a href="/en/training/">workshops page</a> for more end-to-end learning opportunity.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Docker Compose Profiles, one the most useful and underrated features]]>https://event-driven.io/en/docker_compose_profiles/https://event-driven.io/en/docker_compose_profiles/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0b1ec18fed5441a1f769767fdd9e587d/a331c/2024-05-18-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4hAAAuIQEHW/z/AAAC6UlEQVQ4y22T709TVxzG7//jH7D3vjRx77cly0xcjOJYmC9MFBd1OkwMJM2cOqUIQ52MbQHF1pSOX4oFqZVaoFBo6Q/a0kJvuf1xe3vvPfdjei/WN36Tk3O+OTlPnuf5PkdSSwoWTumGRb1h8LEaSg2t3mj3iqmhH54ty+JzJR24uklvFdEEeP58yB9dP5DMKijZXZZOfc1ixwlqco1oMc7tix2M/HOPeouCcEA/ATu7JFY8hAMJIut7eHov8e7uOSaevyY2OU9u8BrbrkvEF8OMBT2s3O/h8a2fiWoF+7GwxCHoJ3Bp+t4Ia8E0+wcq/Zd/Yej6Vab/f8N+Zo+Y30PU50XO7BOQY3Te/4lfx10c0LQJWbZ0ME0LQ1gIYSFFoiVM4ZDOl5us79TafpSLZeTd0qEgwbScJq4d2L1pNtEb6bbUtocL58+QzeTQgN6Bfk52nmFTlu3L0btDDPT+bvuVqJlMjf/HbDCMYiMq1HYeUMwVeDIh434R43VyB0m5dZbQfIh/IzHOfXOM88e/4PLgMFQh2N/HkrsPKyt4mNTIeV08CwSYKbcQDZ5v5PhuaJkjX01w9PYq/ngS6UpXB8GVBHPhVbq+/ZIr3x+nz+2mRXl7+DGpR39BXmckDz3+EHf8AdZU0KoKne/KdHkW6B/9m7MbgqnELtLAmJc92RbBaiJOcHOTekO1Zb6ZeEVgbJbWDEoG3AwUmF5LOxlVVUaXt/htB37cgBMLRUKpPFLPxdPoasYJdmoL9X2gpQaDClZkAKLDCFTqdQ1qCarVErrhTDGTL/BsNcmdcIbZ9SRYAmncdYFmdRGaTSpvJ5HnBrH2FBQ8GE8voPu7kZkkup3DjI+R2lwgvV9vT9XSm6Br7WhL6svroIURDR3WgrDsg0KFCj6iqW42st1UmCEcy5Cdc7Oy5CNbVp1gC6sNJD4Gu7ruRTScbInQW0yfF6p1DFRmwtd4GbnRMoNCSeHBo2GmXs23//Ln1gfvq5sg1cHBpAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 05 18 cover" title="2024 05 18 cover" src="/static/0b1ec18fed5441a1f769767fdd9e587d/a331c/2024-05-18-cover.png" srcset="/static/0b1ec18fed5441a1f769767fdd9e587d/36ca5/2024-05-18-cover.png 200w, /static/0b1ec18fed5441a1f769767fdd9e587d/a3397/2024-05-18-cover.png 400w, /static/0b1ec18fed5441a1f769767fdd9e587d/a331c/2024-05-18-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://www.event-sourcing.dev/about/">Erik Shafer</a> asked me on the <a href="https://discord.gg/fTpqUTMmVa">Emmett Discord</a> if I could provide a sample of how to run the WebApi application using <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a>.</strong> Of course, I said: <em>sure will!</em> I already had <a href="https://github.com/event-driven-io/emmett/tree/main/samples/webApi/expressjs-with-esdb">WebApi sample in the repository</a> I also explained here <a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a>. Easy peasy, then, right?</p> <p>Indeed, it wasn’t that hard. Of course, I had to fight with <a href="/en/how_to_tackle_esmodules_compatibility_issues/">ESModules quirks</a>, but I also expanded the scope a bit, as I also decided to use one of the most underrated Docker Compose features: <a href="https://docs.docker.com/compose/profiles/">Profiles</a>.</p> <h2 id="what-are-docker-compose-profiles" style="position:relative;"><a href="#what-are-docker-compose-profiles" aria-label="what are docker compose profiles permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What are Docker Compose Profiles?</h2> <p>They’re a way to logically group services inside your Docker Compose definition, allowing you to run a subset of services. My original Docker Compose definition contained the <a href="https://developers.eventstore.com/">EventStoreDB</a> startup, which I use in my Emmett samples as the real event store example.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">'3.5'</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">eventstoredb</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> eventstore/eventstore<span class="token punctuation">:</span>23.10.0<span class="token punctuation">-</span>bookworm<span class="token punctuation">-</span>slim <span class="token key atrule">container_name</span><span class="token punctuation">:</span> eventstoredb <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> EVENTSTORE_CLUSTER_SIZE=1 <span class="token punctuation">-</span> EVENTSTORE_RUN_PROJECTIONS=All <span class="token punctuation">-</span> EVENTSTORE_START_STANDARD_PROJECTIONS=true <span class="token punctuation">-</span> EVENTSTORE_EXT_TCP_PORT=1113 <span class="token punctuation">-</span> EVENTSTORE_HTTP_PORT=2113 <span class="token punctuation">-</span> EVENTSTORE_INSECURE=true <span class="token punctuation">-</span> EVENTSTORE_ENABLE_EXTERNAL_TCP=true <span class="token punctuation">-</span> EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">'1113:1113'</span> <span class="token punctuation">-</span> <span class="token string">'2113:2113'</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> volume <span class="token key atrule">source</span><span class="token punctuation">:</span> eventstore<span class="token punctuation">-</span>volume<span class="token punctuation">-</span>data <span class="token key atrule">target</span><span class="token punctuation">:</span> /var/lib/eventstore <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> volume <span class="token key atrule">source</span><span class="token punctuation">:</span> eventstore<span class="token punctuation">-</span>volume<span class="token punctuation">-</span>logs <span class="token key atrule">target</span><span class="token punctuation">:</span> /var/log/eventstore <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> esdb_network <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token key atrule">esdb_network</span><span class="token punctuation">:</span> <span class="token key atrule">driver</span><span class="token punctuation">:</span> bridge <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token key atrule">eventstore-volume-data</span><span class="token punctuation">:</span> eventstore<span class="token punctuation">-</span>volume<span class="token punctuation">-</span>logs<span class="token punctuation">:</span></code></pre></div> <p>Nothing fancy here so far. You can just run it with:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> compose up</code></pre></div> <p>It will start the database; then, you can run a sample application with</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> run start</code></pre></div> <p>And play with Emmett.</p> <p>I wanted to keep the sample experience straightforward and use local development/debugging as the default. Docker image build and run would be optional (we could call it <em>“Erik’s mode”</em>!).</p> <p>Now, profiles come in handy here, as they enable that, I just had to add:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">'3.5'</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">app</span><span class="token punctuation">:</span> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">dockerfile</span><span class="token punctuation">:</span> Dockerfile <span class="token key atrule">context</span><span class="token punctuation">:</span> . <span class="token key atrule">container_name</span><span class="token punctuation">:</span> emmett_api <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>app<span class="token punctuation">]</span> <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ESDB_CONNECTION_STRING=esdb<span class="token punctuation">:</span>//eventstoredb<span class="token punctuation">:</span>2113<span class="token punctuation">?</span>tls=false <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> esdb_network <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">'3000:3000'</span> <span class="token comment"># (...) EventStoreDB Definition</span></code></pre></div> <p>The setup is pretty straightforward.</p> <p>We’re stating which Docker file to use and where it is located.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">dockerfile</span><span class="token punctuation">:</span> Dockerfile <span class="token key atrule">context</span><span class="token punctuation">:</span> .</code></pre></div> <p>Used <strong><em>.</em></strong> means that the build context will be the folder as the location of the docker-compose file. <em>dockerfile</em> tells where the Docker file is located. In our case, it’s the same folder as the docker-compose file, and it’s named <em>Dockerfile</em>. That also opens more options. We could also define it as:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">dockerfile</span><span class="token punctuation">:</span> src/app/Dockerfile <span class="token key atrule">context</span><span class="token punctuation">:</span> .</code></pre></div> <p>That’d allow us to use some common dependencies outside the project folder, e.g. <em>src/build</em>. Essentially, we could expand the image-building process’s access to additional locations and allow project files to access parent folders to use, for instance, shared configurations or dependencies. Thanks go to <a href="https://www.linkedin.com/in/jakubg/">Jakub Gutkowski</a> for <a href="https://www.linkedin.com/feed/update/urn:li:activity:7197619396448546816?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7197619396448546816%2C7197654708721737728%29&#x26;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287197654708721737728%2Curn%3Ali%3Aactivity%3A7197619396448546816%29">pointing that out</a>!.</p> <p>We ensure that we have a connection to EventStoreDB by placing it in the same network and passing the connection string as an environment variable.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"> <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ESDB_CONNECTION_STRING=esdb<span class="token punctuation">:</span>//eventstoredb<span class="token punctuation">:</span>2113<span class="token punctuation">?</span>tls=false <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> esdb_network</code></pre></div> <p>The <em>new thing</em> is the profile definition:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"> <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>app<span class="token punctuation">]</span></code></pre></div> <p>Thanks to that, we’re saying that this service will only be used if we explicitly specify that in the command line. We can, for instance build the image by running</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml">docker compose <span class="token punctuation">-</span><span class="token punctuation">-</span>profile app build</code></pre></div> <p>Or run both EventStoreDB and Emmett WebApi by calling:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml">docker compose <span class="token punctuation">-</span><span class="token punctuation">-</span>profile app up</code></pre></div> <p>And let’s stop here for a moment! Why <strong>both</strong> if I specified the <em>app</em> profile? Docker Compose will run, in this case, specified profile <strong>AND</strong> all services that don’t have a profile specified. That’s quite neat, as we can define the set of default services (e.g. databases, messaging systems, etc.) and add others as optional. Ah, and you can specify multiple profiles by, e.g.:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">docker compose --profile backend --profile frontend up</code></pre></div> <p>You can also group multiple services into a single profile. Why would you do it? Let’s go to…</p> <h2 id="docker-profiles-advanced-scenario" style="position:relative;"><a href="#docker-profiles-advanced-scenario" aria-label="docker profiles advanced scenario permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Docker Profiles advanced scenario</h2> <p><strong>In my <a href="https://github.com/oskardudycz/EventSourcing.NetCore">Event Sourcing .NET samples repository</a>, I’m trying to cover multiple aspects, tools, ways to build Event Sourcing, CQRS and Event-Driven systems.</strong> I’m using:</p> <ul> <li>Marten (so Postgres) and EventStoreDB as example event stores,</li> <li>Postgres and Elasticsearch as read model stores,</li> <li>Kafka used for integration between services,</li> <li>UI tools like PgAdmin and Kafka UI to easier investigate sample data.</li> </ul> <p><strong>Multiple samples are using those services in various configurations.</strong></p> <p>I’m also using them in <a href="/en/training/">my Event Sourcing workshops</a>, so I’d like to ensure the setup is smooth and we can focus on learning and not fighting with Docker.</p> <p>Initially, I kept multiple Docker Compose files for:</p> <ul> <li>default configuration with all services,</li> <li>continuous integration pipeline configuration without UI components, as they’re not needed for tests. They’d just eat resources and make pipeline runs longer. They also don’t have Kafka, as I’m just testing inner modules functionalities,</li> <li>sample web API Docker Image build (similar to the one explained above),</li> <li>only containing Postgres-related configurations,</li> <li>accordingly, only with EventStoreDB,</li> <li>etc.</li> </ul> <p>I’m sure that you can relate that to your projects. Now, how can Docker Compose Profiles help us with that? It could definitely help us merge multiple configurations into one and easier manage updating versions, etc.</p> <p>Let’s see the config I ended up with and then explain the reasoning. I’ll trim the detailed service configuration; you can check the whole file <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/39bc20bdcf2df3480fdf96519682a5c54638bf00/docker-compose.yml">here</a>.</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token comment">#######################################################</span> <span class="token comment"># Postgres</span> <span class="token comment">#######################################################</span> <span class="token key atrule">postgres</span><span class="token punctuation">:</span> <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> postgres<span class="token punctuation">,</span> postgres<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all<span class="token punctuation">,</span> all<span class="token punctuation">-</span>no<span class="token punctuation">-</span>ui<span class="token punctuation">,</span> ci <span class="token punctuation">]</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>15.1<span class="token punctuation">-</span>alpine <span class="token comment"># (...) rest of the config</span> <span class="token key atrule">pgadmin</span><span class="token punctuation">:</span> <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> postgres<span class="token punctuation">-</span>ui<span class="token punctuation">,</span> postgres<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all <span class="token punctuation">]</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> dpage/pgadmin4 <span class="token comment"># (...) rest of the config</span> <span class="token comment">#######################################################</span> <span class="token comment"># EventStoreDB</span> <span class="token comment">#######################################################</span> <span class="token key atrule">eventstore.db</span><span class="token punctuation">:</span> image<span class="token punctuation">:</span> eventstore/eventstore<span class="token punctuation">:</span>23.10.0<span class="token punctuation">-</span>bookworm<span class="token punctuation">-</span>slim <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> eventstoredb<span class="token punctuation">,</span> eventstoredb<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all<span class="token punctuation">,</span> all<span class="token punctuation">-</span>no<span class="token punctuation">-</span>ui<span class="token punctuation">,</span> ci <span class="token punctuation">]</span> <span class="token comment"># (...) rest of the config</span> <span class="token comment">#######################################################</span> <span class="token comment"># Elastic Search</span> <span class="token comment">#######################################################</span> <span class="token key atrule">elasticsearch</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/elasticsearch/elasticsearch<span class="token punctuation">:</span>8.13.2 <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> elastic<span class="token punctuation">,</span> elastic<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all<span class="token punctuation">,</span> all<span class="token punctuation">-</span>no<span class="token punctuation">-</span>ui<span class="token punctuation">,</span> ci <span class="token punctuation">]</span> <span class="token comment"># (...) rest of the config</span> <span class="token key atrule">kibana</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/kibana/kibana<span class="token punctuation">:</span>8.13.2 <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> elastic<span class="token punctuation">-</span>ui<span class="token punctuation">,</span> elastic<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all <span class="token punctuation">]</span> <span class="token comment"># (...) rest of the config</span> <span class="token comment">#######################################################</span> <span class="token comment"># Kafka</span> <span class="token comment">#######################################################</span> <span class="token key atrule">kafka</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> confluentinc/confluent<span class="token punctuation">-</span>local<span class="token punctuation">:</span>7.6.1 <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>kafka<span class="token punctuation">,</span> kafka<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all<span class="token punctuation">,</span> all<span class="token punctuation">-</span>no<span class="token punctuation">-</span>ui<span class="token punctuation">]</span> <span class="token comment"># (...) rest of the config</span> <span class="token key atrule">init-kafka</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> confluentinc/confluent<span class="token punctuation">-</span>local<span class="token punctuation">:</span>7.6.1 <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> kafka<span class="token punctuation">,</span> kafka<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all<span class="token punctuation">,</span> all<span class="token punctuation">-</span>no<span class="token punctuation">-</span>ui <span class="token punctuation">]</span> <span class="token key atrule">command</span><span class="token punctuation">:</span> <span class="token string">"#shell script to setup Kafka topics"</span> <span class="token comment"># (...) rest of the config</span> <span class="token key atrule">schema_registry</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> confluentinc/cp<span class="token punctuation">-</span>schema<span class="token punctuation">-</span>registry<span class="token punctuation">:</span>7.6.1 <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> kafka<span class="token punctuation">-</span>ui<span class="token punctuation">,</span> kafka<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all <span class="token punctuation">]</span> <span class="token comment"># (...) rest of the config</span> <span class="token key atrule">kafka_topics_ui</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> provectuslabs/kafka<span class="token punctuation">-</span>ui<span class="token punctuation">:</span>latest <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> kafka<span class="token punctuation">-</span>ui<span class="token punctuation">,</span> kafka<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all <span class="token punctuation">]</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> kafka <span class="token comment"># (...) rest of the config</span> <span class="token comment">#######################################################</span> <span class="token comment"># Open Telemetry</span> <span class="token comment">#######################################################</span> <span class="token key atrule">jaeger</span><span class="token punctuation">:</span> image<span class="token punctuation">:</span> jaegertracing/all<span class="token punctuation">-</span>in<span class="token punctuation">-</span>one<span class="token punctuation">:</span>latest <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> otel<span class="token punctuation">,</span> otel<span class="token punctuation">-</span>all<span class="token punctuation">,</span> all <span class="token punctuation">]</span> <span class="token comment"># (...) rest of the config</span> <span class="token comment">#######################################################</span> <span class="token comment"># Test Backend Service</span> <span class="token comment">#######################################################</span> <span class="token key atrule">backend</span><span class="token punctuation">:</span> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">dockerfile</span><span class="token punctuation">:</span> Dockerfile <span class="token key atrule">context</span><span class="token punctuation">:</span> . <span class="token key atrule">profiles</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>build<span class="token punctuation">]</span> <span class="token comment"># (...) rest of the config</span> <span class="token comment">## (...) Network and Volumes config</span></code></pre></div> <p>As you see, we have a few general profiles:</p> <ul> <li><em>postgres</em></li> <li><em>elastic</em></li> <li><em>kafka</em></li> <li><em>eventstoredb</em></li> <li><em>otel</em></li> <li><em>build</em></li> </ul> <p>They group the needed tooling containers.</p> <p>Each of them has the additional profiles with prefixes:</p> <ul> <li><em>{profile}-all</em> (e.g. <em>postgres-all</em>) - will start all needed tooling containers plus supportive like ui,</li> <li><em>{profile}-all-no-ui</em> - will start just the needed tooling without UI components. There’s no <em>{profile}-all-ui</em>, as starting UI without actual components doesn’t make sense.</li> </ul> <p>I also defined additional profiles:</p> <ul> <li><em>all</em> - that’ll run all components,</li> <li><em>ci</em> - only components needed for the CI pipeline (so no UI and Kafka).</li> </ul> <p>So by default, if I don’t mind my RAM being eaten by all containers, I’d run:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> compose --profile all up</code></pre></div> <p>If I’d like to run the Marten sample with Elasticsearch read models, I could just run:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml">docker compose <span class="token punctuation">-</span><span class="token punctuation">-</span>profile postgres <span class="token punctuation">-</span><span class="token punctuation">-</span>profile elastic up</code></pre></div> <p>In the CI, I can run:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml">docker compose <span class="token punctuation">-</span><span class="token punctuation">-</span>profile ci up</code></pre></div> <p><strong>It’s important to find balance and conventions for profile names.</strong> If you have too many of them, it’ll be challenging for people to memorise all of them. That’s why grouping them and adding standard conventions can be helpful. We should always consider intended usage and make it accessible. I could potentially provide profiles for dedicated samples.</p> <p>Read more in the official <a href="https://docs.docker.com/compose/profiles/">Docker Compose Profiles guide</a>.</p> <p>See also the Pull Requests where I introduced explained changes to:</p> <ul> <li><a href="https://github.com/event-driven-io/emmett/pull/67">Emmett</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/258">EventSourcing .NET</a>.</li> </ul> <p>If you get to this place, then you may also like my other articles around Docker and Continuous Integration:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/marten_and_docker">How to create a Docker image for the Marten application</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to write a left-fold streams collector in Java]]>https://event-driven.io/en/how_to_write_left_fold_collector_in_java/https://event-driven.io/en/how_to_write_left_fold_collector_in_java/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8c724b354c0146997b36f2d37f7205fd/a331c/2024-05-09-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4hAAAuIQEHW/z/AAADiklEQVQ4yxXGW09aBwDA8fMB9h32tmSvS1+a7KFpk13jslsbu7kt7dZ5eVinNY22WZzG1lk3tbXzUtNVq1hx6LzhDRWRiwgIRzgCBxDwBoogeAFxav/Lfk8/wah8jGmiD4sosXZ8RvhgH8/eMc5YBjmVJpw9JXx4xGoiQTh7hpzK4NzJ4I5n8CTTBBJHeNb28AYieLdTCMrGMl7U3kE/P4OoVeDQD+LeimGLZPAfneCPrLE00shsexmWBS2r2XPciUOkxDFy6l988g6yPYRjYgbz0ChCbUkR49OLGOwS6oYS1E8rsRvGWd4+w5/cxzL6jIH7XzL1pIhpZQee5Cn+dAbvbgKbUoVLZ8YfSOG2urENDyOI0XPEjTT2ZRc2/Sia1kqMQ53oXHu4wpvM/PUrL8u/QzSNM6UxYPLGCcVPCIQiaO4Wo2v6DbcjQMAbR9KZEFweB4E0GEUfWs0U+pFOOhvu8WrMiTWYYbKtAqPqKV3NjbS3drO8fsyiUs3SwCB+uwfnzBy2ETWyGGJViiCM1d3CNNaL7wDM8i7qoQFUbdUYDTrE6GvGNYv8/Oklij64iD+WxBc/xrW6jlVcwTChx9jbj0nRg31Mg3fBidBTUYSmpQyLxULoBMx2G3+WXGeo+ib2JRvDujUe1jxnXm9iK31CMJ5mNZ5Bjh4ypxdpufMTT77Oob+iHFNHJ8KkopmRxlJm1SoCsQNe1ZdT+dVlBh/fY6Kvkxn7LtZQFkkOEIxn8YQiuMMRQv8/sIGm/yW1377H7YtvUvXJJYQHpYW0VRQwq9WzJEfRdv9BX305k92NPKurYcQQxO5aRpKD+HeyWGwiLv8mvq19/JFDVkIxZidneFR4ldy33kBoV8zRpRhgoqMOq3mBf5S93L3xGdeuvMsPX+Qy782gX7Aw2afApJnCYnUibySRfGtI/nWkUAJXMInZZKK3pQHBGQfvWozpjof0Vhcwqurj1jc/knMlh+bn49g2ztEux/glv4jCjy/TUFJAf30VZs0YKysrrMirSIEodlHCaDAieFJnmNVdzP3dTGtxHuN93fT069H7XqOXj1kMp7FtZJl3xGhrGyX/ehH573/I/c8/4tH311A11WCYULOg02I2zCF4t7ZQVeXSlPcOL0pzaa55QK9axLZ5inX9EHE7gyOaRrKGkOddmE0hBueidCnNFN8o4eqFC+RdeJvfb99E0VDNf+6MOR0+DrMqAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 05 09 cover" title="2024 05 09 cover" src="/static/8c724b354c0146997b36f2d37f7205fd/a331c/2024-05-09-cover.png" srcset="/static/8c724b354c0146997b36f2d37f7205fd/36ca5/2024-05-09-cover.png 200w, /static/8c724b354c0146997b36f2d37f7205fd/a3397/2024-05-09-cover.png 400w, /static/8c724b354c0146997b36f2d37f7205fd/a331c/2024-05-09-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="/en/this_is_not_your_uncle_java/">Last week, we covered the latest improvements to Java 22 around pattern matching and records.</a></strong> They enable explicit business logic modelling, making it concise and guarded by the compiler. As usual, I put it into the context of Event Sourcing.</p> <p>We ended it with two functions. First one evolveing the state based on the event:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> <span class="token function">evolve</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> state<span class="token punctuation">,</span> <span class="token class-name">Event</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token function">when</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Initial</span> _<span class="token punctuation">,</span> <span class="token class-name">Opened</span> _<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token class-name">ProductItems</span><span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span>productItems<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span>productItems<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Confirmed</span> _<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Canceled</span> _<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Closed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">default</span> <span class="token operator">-></span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The other taking an array of events into the final state:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">Initial</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token class-name">ProductItems</span> <span class="token class-name">ProductItems</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Closed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> <span class="token function">getShoppingCart</span><span class="token punctuation">(</span><span class="token class-name">Event</span><span class="token punctuation">[</span><span class="token punctuation">]</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">ShoppingCart</span> state <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Initial</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> event <span class="token operator">:</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> state <span class="token operator">=</span> <span class="token function">evolve</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p>Essentially, we’re:</p> <ol> <li>Reading the list of events.</li> <li>Creating the initial state.</li> <li>We’re evolving the new state by applying the events to the current state for each event.</li> <li>We repeat that for each event, getting the final state.</li> </ol> <p><strong>This process is called Left Fold/Aggregation/Reduce (depending on who phrase it).</strong></p> <p>That’s fine, but some hip kids don’t like to create such functions for each state. Cool kids of Java like streams. At least, that’s what I heard. Wanna be like them?</p> <p>Wouldn’t it be nice if we could do something like:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCartService</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> <span class="token function">getShoppingCart</span><span class="token punctuation">(</span><span class="token class-name">EventStore</span> eventStore<span class="token punctuation">,</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> events <span class="token operator">=</span> eventStore<span class="token punctuation">.</span><span class="token function">readStream</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart<span class="token punctuation">.</span>Event</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> events<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span><span class="token function">foldLeft</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart<span class="token punctuation">.</span>Initial</span><span class="token operator">::</span><span class="token keyword">new</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span><span class="token operator">::</span><span class="token function">evolve</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>That’s possible but not as easy as it seems. There is no left-fold collector that’d aggregate values on top of the element (state) respecting the order. You can’t use standard Java Stream collectors like <em>Collectors.toList()</em> or <em>Collectors.toMap()</em> for this. Streams API is built to enable both parallel and sequential processing, and that’s great for reactive processing but not great for our case, where we don’t need to parallelize that but just iterate the aggregating state. Knowing that we can simplify the processing, but to do that, we still need to go custom.</p> <p>Let’s try, then and build a custom stream collector! Being a cool kid is not only about perks; it also requires some work upfront!</p> <p>Let’s see first the whole implementation and tackle its pieces one by one:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FoldLeft</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token keyword">implements</span> <span class="token class-name">Collector</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span></span> getInitial<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">BiFunction</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> evolve<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token class-name">FoldLeft</span><span class="token punctuation">(</span><span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span></span> getInitial<span class="token punctuation">,</span> <span class="token class-name">BiFunction</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> evolve<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>getInitial <span class="token operator">=</span> getInitial<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>evolve <span class="token operator">=</span> evolve<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token class-name">FoldLeft</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token function">foldLeft</span><span class="token punctuation">(</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span></span> getInitial<span class="token punctuation">,</span> <span class="token class-name">BiFunction</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> evolve <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">FoldLeft</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>getInitial<span class="token punctuation">,</span> evolve<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">supplier</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">AtomicReference</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>getInitial<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">BiConsumer</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token function">accumulator</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>wrapper<span class="token punctuation">,</span> event<span class="token punctuation">)</span> <span class="token operator">-></span> wrapper<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>evolve<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">BinaryOperator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">combiner</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>left<span class="token punctuation">,</span> right<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> left<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>right<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> left<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">Function</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> <span class="token function">finisher</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token class-name">AtomicReference</span><span class="token operator">::</span><span class="token function">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">Set</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Characteristics</span><span class="token punctuation">></span></span> <span class="token function">characteristics</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">HashSet</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s not that much code, but it still can be a bit confusing if that’s the first time we’re implementing a custom collector. That’s fine, as we probably should not be doing that too often!</p> <p><strong>The custom collector, you need the following ingredients:</strong></p> <ol> <li><strong>Supplier:</strong> Provides an initial value for the accumulation (an empty shopping cart in your case).</li> <li><strong>Accumulator:</strong> Applies an event to the current state of the shopping cart, evolving the state based on the event type.</li> <li><strong>Combiner:</strong> Merges two states, which is crucial for parallel processing. In our case, it’s just a simple value replacement. We’re saying that the evolved state is now the current accumulated one.</li> <li><strong>Finisher:</strong> Extracts the final state from the accumulator’s container.</li> <li><strong>Characteristics:</strong> Provide hints to the implementation about the collector’s characteristics, such as whether it is CONCURRENT or UNORDERED. In our case, we return an empty set, implying none of these characteristics are explicitly claimed.</li> </ol> <p>We’re starting by stating that we’re defining the custom collector by implementing <em>Collector&#x3C;Event, AtomicReference<Entity>, Entity></em></p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FoldLeft</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token keyword">implements</span> <span class="token class-name">Collector</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p>It means that our stream will contain values of <em>Event</em> type and we’re expecting instance of <em>Entity</em> as a result. We’ll be accumulating values to <a href="https://www.baeldung.com/java-atomic-variables">AtomicReference<Entity></a>, ensuring that updates to the state are thread-safe, which is crucial if your stream processing might be parallelised.</p> <p>Then, we’re providing functions that will return the initial state and the way to evolve it based on events. I added a static method to make usage less verbose.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span></span> getInitial<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">BiFunction</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> evolve<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token class-name">FoldLeft</span><span class="token punctuation">(</span><span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span></span> getInitial<span class="token punctuation">,</span> <span class="token class-name">BiFunction</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> evolve<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>getInitial <span class="token operator">=</span> getInitial<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>evolve <span class="token operator">=</span> evolve<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token class-name">FoldLeft</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token function">foldLeft</span><span class="token punctuation">(</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span></span> getInitial<span class="token punctuation">,</span> <span class="token class-name">BiFunction</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> evolve <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">FoldLeft</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>getInitial<span class="token punctuation">,</span> evolve<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Then, we’re overriding all the methods. Supplier with the initial state.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">supplier</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">AtomicReference</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>getInitial<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Accumulator with a function how to evolve state:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">BiConsumer</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token function">accumulator</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>wrapper<span class="token punctuation">,</span> event<span class="token punctuation">)</span> <span class="token operator">-></span> wrapper<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>evolve<span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>wrapper<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Combiner that’ll set the new (evolved) state value:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">BinaryOperator</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">combiner</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>left<span class="token punctuation">,</span> right<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> left<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>right<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> left<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Finisher returns the final value, taking the value from our atomic reference wrapper.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token class-name">Function</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">AtomicReference</span><span class="token punctuation">&lt;</span><span class="token class-name">Entity</span><span class="token punctuation">></span><span class="token punctuation">,</span> <span class="token class-name">Entity</span><span class="token punctuation">></span></span> <span class="token function">finisher</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token class-name">AtomicReference</span><span class="token operator">::</span><span class="token function">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And boom, we created our own reusable left-fold collector, which we can use elsewhere for different entity types or even not for Event Sourcing!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[This is not your uncle's Java! Modelling with Java 22 records pattern matching in practice]]>https://event-driven.io/en/this_is_not_your_uncle_java/https://event-driven.io/en/this_is_not_your_uncle_java/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8414046d23c406e48554c300363a6976/a331c/2024-05-04-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4hAAAuIQEHW/z/AAADJElEQVQozwEZA+b8AL5zU7NpR65lQ6ZfPKddOI5XPJCLi8Cxr8SppK+UkI90b5JxbIBiXW5UT39gWHBVTElDQmBBNK1kQKJfPAC2aEasXjuiVzOfVjOlWTaUbVuxsrTYvLTmr5/esqi4koiGaWN3V1CDW1CQYlWDWElZT0pwTTyyZkKhYDwA5Mi74MS338K2zqaUoFg2qZWMycvN3byy66qW8sa678CyzpyLuol4wYRtpWdNj1c9cGReclFCsmZBpGA8AP////rl5v/y9OfZ0ZhQL76Of8a8u9mypemvm+q8ruy1qeWoltqahsR/Z6xqUY9WPWNWT41gSq5hPaNfPAD8///ptrb76evf0suUTS29dFnLopXDoZfot6XbsqfOoJXIjH7VlYa9hXSKWkZ4SzRiQTKuZkanXzykXz0A7vX9wtLm5O351cnGjUkos3NY9MCtsop8yJWD1amas4h4yJB/yY5+hV5RdE49cEgzgUwzsmZBol06n1s4AOby/LfT69Xu/+PVzY5KKadnTOeumeG6qqqHepFuYIViVMiaireHc25JOHRWSGVDMXJELKthPaJdO5pZNgDx09bvzc3y09fau7STUzONSCe8gGTsvauefXWob1qnb1egfnOtlI5qQjCATjhTNCVwQSmsYj2eVzOdVjIA88fI8MPC+NDR3biwllM0h0EhqXZd7Lik16eYsXttpHVq2qujvoV3dUk5hlVFg1tJfGdZdltKg2BLhl1IALqIcrWHb7SCaqRrUZFaQqSQh762suCumuiwnMyUg+K3st+oorp+cqJlVKBjTIpmVDxBQGNmYYOHhGlzcQCPRCOSTC6LYE2VfG+rp6OGjovP1NTDoY7PkXjLmYbjtanamJGuaWGPW0qKVj6KcV92cWZYWFF2dnZ6engAqYZ0qqGaoKinwMbIj46ISlFKsLCloZiFxJJ5yJB31JmHyIl7llpJekozgFI6Z1pNgnxuWVlPNjw2QUhDAH+EfoeJg5ybnJ+foHFtaYeJhby5q4+Oe7ilmbmAZ6p4ZI5hUWtFM4NTO35YRHh1aVhVUEVFQjA2Lzk+NmWsoX51+ukdAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 05 04 cover" title="2024 05 04 cover" src="/static/8414046d23c406e48554c300363a6976/a331c/2024-05-04-cover.png" srcset="/static/8414046d23c406e48554c300363a6976/36ca5/2024-05-04-cover.png 200w, /static/8414046d23c406e48554c300363a6976/a3397/2024-05-04-cover.png 400w, /static/8414046d23c406e48554c300363a6976/a331c/2024-05-04-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I like learning new things. It stimulates my creativity, helps me gain diverse perspectives, and helps me be humble.</strong> When you’re a notorious debutant, you learn to appreciate small stuff and simplicity, the <a href="/en/power_of_ignorance/">power of ignorance</a>. It shows that if you’re down the rabbit hole, then this works both ways. Not many people could go the same way, but it also takes time to get out of that and embrace the outside world.</p> <p>Still, I not only like to scratch the surface but also learn idiomatic ways and check how far I can go. Some time ago, <a href="/en/12_things_I_learned_on_last_pull_request_review/">I got back to doing more Java</a>, and it was a good move. This is not only because it appears that many of my clients after I went solo, are from JVM land but also because the language is changing rapidly. And those changes are made quickly and with a proper dose of consideration. I like reading JDK Enhancements Proposals. Even if you don’t like Java, they’re written in a really accessible way, so it’s worth reading them to learn more about decision-making and see how languages and environments are evolving.</p> <p><strong>This is not your uncle’s Java anymore!</strong></p> <p>If you’re a frequent reader of my blog, you may have noticed that I use the Shopping Cart example frequently. And that’s intentional; I think it’s worth not doing it all at once. If I’m learning new technical aspects, I wouldn’t like to crunch the business domain simultaneously. That also works the other way; it’s safer to use a boring tech stack if I’m diving into a new domain. That reduces cognitive load.</p> <p><strong>In my opinion, one of the best ways to learn a new technology or language is to have some sort of <a href="https://en.wikipedia.org/wiki/Kata">Kata</a>.</strong> So, the topic we discussed many times is that we don’t need to think about mechanics. For me, Kata is a shopping cart. It’s straightforward enough, so I don’t need to think too much about the domain, but it shows the common cases that can happen during implementation (consistency, concurrency, nested data, integration with other flows). It allows me to <a href="/en/dive_a_bit_deeper_look_a_bit_wider/">dive a bit deeper</a> if I’d like to, e.g. to analyse integrations with other components, high traffic during Black Friday etc.</p> <p>Still, getting back to not-your-uncle Java. Let’s discuss the latest records and pattern-matching enhancements added in Java 22. As mentioned, we’ll use my event-sourced shopping cart Kata. Please check <a href="/en/how_to_effectively_compose_your_business_logic/">How to effectively compose your business logic</a> if you want to learn more about the domain.</p> <p>Let’s start this time with the end result and then explain what actually happened.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartDecider</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>Event</span> <span class="token function">decide</span><span class="token punctuation">(</span><span class="token class-name">Command</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> state<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token function">on</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> command<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span><span class="token class-name">Initial</span> _<span class="token punctuation">,</span> <span class="token class-name">Open</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> clientId<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Opened</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">AddProductItem</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">RemoveProductItem</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>productItems<span class="token punctuation">.</span><span class="token function">hasEnough</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Not enough product items to remove"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Confirm</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Confirmed</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Cancel</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Canceled</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">default</span> <span class="token operator">-></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"Cannot %s on %s"</span><span class="token punctuation">,</span> command<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> state<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>This method is responsible for making business decisions on our shopping cart.</strong> We can:</p> <ul> <li>open a new shopping cart (if it wasn’t already opened),</li> <li>add product item to not closed (so not confirmed or cancelled) shopping cart,</li> <li>remove products if we added them already,</li> <li>confirm or cancel the pending cart.</li> </ul> <p>Our function takes the current state and the command representing our intention. Both of them are immutable objects defined as union types.</p> <p>Shopping Cart can be represented by the following states:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">Initial</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token class-name">ProductItems</span> <span class="token class-name">ProductItems</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Closed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>I’m using <a href="https://openjdk.org/jeps/409">sealed interface</a> here, which allows me to say that only those three states can represent Shopping Carts.</strong> What’s more, that’s also an enabler for advanced pattern matching. Notice that states contain only information that’s needed for the business logic. To make it more focused, we’re <em>outsourcing</em> the logic to <em>ProductItems</em> value object, which is defined as:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProductItems</span> <span class="token punctuation">{</span> <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Integer</span><span class="token punctuation">></span></span> values<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">ProductItems</span><span class="token punctuation">(</span><span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Integer</span><span class="token punctuation">></span></span> values<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>values <span class="token operator">=</span> values<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ProductItems</span> <span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ProductItems</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token class-name">ProductItems</span> <span class="token function">add</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> newValues <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>values<span class="token punctuation">)</span><span class="token punctuation">;</span> newValues<span class="token punctuation">.</span><span class="token function">compute</span><span class="token punctuation">(</span><span class="token function">key</span><span class="token punctuation">(</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> currentQuantity<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Optional</span><span class="token punctuation">.</span><span class="token function">ofNullable</span><span class="token punctuation">(</span>currentQuantity<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">orElse</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> productItem<span class="token punctuation">.</span>quantity <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ProductItems</span><span class="token punctuation">(</span>newValues<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token class-name">ProductItems</span> <span class="token function">remove</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> newValues <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>values<span class="token punctuation">)</span><span class="token punctuation">;</span> newValues<span class="token punctuation">.</span><span class="token function">compute</span><span class="token punctuation">(</span><span class="token function">key</span><span class="token punctuation">(</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> currentQuantity<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">Optional</span><span class="token punctuation">.</span><span class="token function">ofNullable</span><span class="token punctuation">(</span>currentQuantity<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">orElse</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">-</span> productItem<span class="token punctuation">.</span>quantity <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ProductItems</span><span class="token punctuation">(</span>newValues<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">hasEnough</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> currentQuantity <span class="token operator">=</span> values<span class="token punctuation">.</span><span class="token function">getOrDefault</span><span class="token punctuation">(</span><span class="token function">key</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> currentQuantity <span class="token operator">>=</span> productItem<span class="token punctuation">.</span><span class="token function">quantity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">key</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> pricedProductItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"%s_%s"</span><span class="token punctuation">,</span> pricedProductItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> pricedProductItem<span class="token punctuation">.</span><span class="token function">unitPrice</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">PricedProductItem</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> productId<span class="token punctuation">,</span> <span class="token keyword">int</span> quantity<span class="token punctuation">,</span> <span class="token keyword">double</span> unitPrice <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s also an immutable object but modelled as a class not to expose internal information. Product Item is represented by its product id and unit price. We can have the same product at different prices (e.g., some have a discount applied). To simplify business logic, we don’t need to maintain a list of objects; it’s fine to use a map where the key is the identifier built from product id unit price, and value quantity. We allow negative quantity to simplify processing, as we’ll check that in the business logic through the exposed <em>hasEnough</em> method.</p> <p>Speaking about the business logic, we can define the API as the following set of commands:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartDecider</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">Open</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">AddProductItem</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">RemoveProductItem</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Confirm</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Cancel</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>I define the Command type without prefix, as having it nested gives enough information about the context.</strong> That’s also why I don’t use suffixes like <em>CancelShoppingCart</em> but just name it <em>Cancel</em>.</p> <p>In Event Sourcing outcome of the business logic is event or set of events, let’s define them. We can do it accordingly to commands, and put them inside Shopping Cart class, as they’re strictly related to shopping cart lifetime.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">Opened</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> openedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">ProductItems<span class="token punctuation">.</span>PricedProductItem</span> productItem<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> addedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">ProductItems<span class="token punctuation">.</span>PricedProductItem</span> productItem<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> removedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Confirmed</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> confirmedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Canceled</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> canceledAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Each event is appended to the stream. An event stream is a history of the record. It keeps all results (facts) of what has happened. It can look as follow:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Id: "shopping_cart-1294f9" Events: 1. Opened 2. ProductItemAdded 3. ProductItemRemoved 4. ProductItemAdded 5. Confirmed</code></pre></div> <p>When we want to run the next decision, we need to build the current state from events. We read all of them and apply them one after another, evolving the state into its final form. You can do it the following way:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token class-name">ShoppingCart</span> <span class="token function">getShoppingCart</span><span class="token punctuation">(</span><span class="token class-name">Event</span><span class="token punctuation">[</span><span class="token punctuation">]</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">ShoppingCart</span> state <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Initial</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> event <span class="token operator">:</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> state <span class="token operator">=</span> <span class="token function">evolve</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>What would the evolve function look like? Similarly to the <em>decide</em> presented first:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> <span class="token function">evolve</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> state<span class="token punctuation">,</span> <span class="token class-name">Event</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token function">when</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Initial</span> _<span class="token punctuation">,</span> <span class="token class-name">Opened</span> _<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token class-name">ProductItems</span><span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span>productItems<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span>productItems<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Confirmed</span> _<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Canceled</span> _<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Closed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">default</span> <span class="token operator">-></span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re defining the expected state transitions/evolutions; we just return the state for other cases. Why am I not throwing an exception here? Read more in <a href="/en/should_you_throw_exception_when_rebuilding_state_from_events/">Should you throw an exception when rebuilding the state from events?</a>. Check also the <a href="/en/how_to_write_left_fold_collector_in_java">follow-up article showing how to do make it generic using a custom streams collector</a>.</p> <p>And let’s stop here and explain a few stuff.</p> <p>What’s actually this code?</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token function">when</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>In Java 22, you can do pattern matching on records, yet the switch can take just a single object; here, we’d like to get the permutations of the potential state and events evolving them.</strong> Java doesn’t have tuples like e.g. <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples">C#</a> or <a href="https://blog.logrocket.com/exploring-use-cases-typescript-tuples/">TypeScript</a>. You need to be explicit and define dedicated types. From what I heard recently from Brian Goetz, that’s intentional. See more:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/bKwzONOGLxs?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>So, let’s define some explicit records to use them in switch!</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FunctionalTools</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token class-name">When</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token function">when</span><span class="token punctuation">(</span><span class="token class-name">State</span> state<span class="token punctuation">,</span> <span class="token class-name">Event</span> event<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">When</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token class-name">On</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">State</span> state<span class="token punctuation">,</span> <span class="token class-name">Event</span> event<span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">On</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">When</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token class-name">State</span> state<span class="token punctuation">,</span> <span class="token class-name">Event</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">On</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token class-name">State</span> state<span class="token punctuation">,</span> <span class="token class-name">Command</span> command<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see, those records are just wrappers for the values. They’re explicit and named to be a bit more readable in the intended usage. I also added some helper factory functions to reduce the noise of generic record setup in the end usage.</p> <p>Now, we can use the fabulous feature which is static import, e.g.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">import</span> <span class="token keyword">static</span> <span class="token class-name">FunctionalTools<span class="token punctuation">.</span>When</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token keyword">static</span> <span class="token class-name">FunctionalTools</span><span class="token punctuation">.</span>when<span class="token punctuation">;</span></code></pre></div> <p>Et voilà! Now, we can use it in the switch statement like:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token function">when</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span>productItems<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>This means we’re creating a <em>When</em> record using the <em>when</em> function from state and event. Then we can destructure it and say that having <em>When</em> record with the first property of type Pending (in other words, pending shopping cart) and the second parameter of type <em>ProductItemAdded</em> (applying this event on the pending state), we should run the following code. In this case, return the new Pending shopping cart with the added product item.</p> <p><strong>What’s more, this type of check is exhaustive, so if we don’t provide a default case, the compiler will tell you that you didn’t provide all permutations!</strong></p> <p>As you see, a bit of explicit code and intentional design can create a straightforward, declarative definition of our code. Much shorter than the imperative one. Of course we should be careful not to make it cryptic. The intention is to have explicit business logic, not sneaky, smart code.</p> <p>Let’s show the final code again. Decider, for running business logic:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartDecider</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">Open</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">AddProductItem</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">RemoveProductItem</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Confirm</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Cancel</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Command</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>Event</span> <span class="token function">decide</span><span class="token punctuation">(</span><span class="token class-name">Command</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> state<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token function">on</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> command<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span><span class="token class-name">Initial</span> _<span class="token punctuation">,</span> <span class="token class-name">Open</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> clientId<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Opened</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">AddProductItem</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">RemoveProductItem</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>productItems<span class="token punctuation">.</span><span class="token function">hasEnough</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Not enough product items to remove"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Confirm</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Confirmed</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">On</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Cancel</span><span class="token punctuation">(</span><span class="token keyword">var</span> id<span class="token punctuation">,</span> <span class="token keyword">var</span> now<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Canceled</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">default</span> <span class="token operator">-></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span> <span class="token class-name">String</span><span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"Cannot %s on %s"</span><span class="token punctuation">,</span> command<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> state<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>And shopping cart defining our state and its evolution:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">Initial</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token class-name">ProductItems</span> <span class="token class-name">ProductItems</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Closed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">Opened</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> openedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">ProductItems<span class="token punctuation">.</span>PricedProductItem</span> productItem<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> addedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">ProductItems<span class="token punctuation">.</span>PricedProductItem</span> productItem<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> removedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Confirmed</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> confirmedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">Canceled</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> canceledAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">Event</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> <span class="token function">getShoppingCart</span><span class="token punctuation">(</span><span class="token class-name">Event</span><span class="token punctuation">[</span><span class="token punctuation">]</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">ShoppingCart</span> state <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Initial</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> event <span class="token operator">:</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> state <span class="token operator">=</span> <span class="token function">evolve</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> <span class="token function">evolve</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> state<span class="token punctuation">,</span> <span class="token class-name">Event</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token function">when</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Initial</span> _<span class="token punctuation">,</span> <span class="token class-name">Opened</span> _<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token class-name">ProductItems</span><span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span>productItems<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span><span class="token keyword">var</span> productItems<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token keyword">var</span> productItem<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span>productItems<span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Confirmed</span> _<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">When</span><span class="token punctuation">(</span><span class="token class-name">Pending</span> _<span class="token punctuation">,</span> <span class="token class-name">Canceled</span> _<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">Closed</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">default</span> <span class="token operator">-></span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>I encourage you to play with those features. They can be super useful not only for Event Sourcing but also for domain modelling in general.</p> <p><strong>One of the possible options is to go through my recently updated Event Sourcing self-paced kit. See more in <a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/workshops/introduction-to-event-sourcing">my repository</a>. Or maybe doing a <a href="https://event-driven.io/en/training/">full workshop with me</a>.</strong></p> <p>Yay or nay?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to configure a custom Test Container on the EventStoreDB example]]>https://event-driven.io/en/custom_test_container_on_esdb_example/https://event-driven.io/en/custom_test_container_on_esdb_example/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/15bd9a552317ca7f1a7c2f5fb6bcf305/a331c/2024-04-28-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4iAAAuIgGq4t2SAAACeklEQVQoz31RXUhTcRT/C2ZIPUS99FBQUg+9hETkS71mEQQ9RAQhIVEEWRBXsBxKFglOR22Jqfkxm2YG1vVjuu2q29S7uev8mJuLrZltOu/U6T5cTu+9/xN3MxOyfk/ncA7n/D4Q7IAgYADAeKvFCcA/gAEj2ORJk33IMZPo/wcMAhZXsIC5RAGoQm9JbWzbX6ZspazsLMv6g3PeQDi0tjC3tDC3zPqDiwsry4uhaDj25woWAMDwVYEeDZoQM4CISkX74JTZ2fHJoNeMDlJjXW0Go86qIWkNSVNdJvu4dyXycf3n9Eqk0xGq80VoovUo8gWCud0DRLMushpN6tyFMOYBYHY+Rzt9qMZ1omgKvZrMeKhKQ+KQT8jZ9gHw1hUsMuSFDQDwLJc2uzMkE0j17TI5+fg5k5bfmYIEQQhHwzzP7eowBlHe0sak1Jn6znWmh82pGT2fe73wtfaihEaopUX1VFKooyj/vI/nNnfEJlK1+cg6w80OGyF1orcDV+/dksj6s8rJ7Fpz9ov+w6iqurq2samn3/jju6fhvYphLIsBluf55P83fdduVKG79alPNOjl8AHZh9sln0/nNKCSnpOV5nOiZpvdoe7VCBgkFTVqSu92ObWaXqvVAgDto/l36tGDpvT7yj2EDsmZUzJ9JqFGEmNKsTEdJRiKwliWzSMKaPMIADBTrnK5wu/1z4cmusdLvzBF7abicm1W2fCxMsPxZzRSejJb3ZfQtk8cx61Fo7FYLB5fbyPVMrlixuNOjnhhk+PjHLfRMHYlj0RKx4VqW2bp8EH0V6Q4Ho+PWCx9lDYQCPxOHiej9q6OFHTtLTHsk9JH5ENnfwF3aW0X7ZRCGQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 04 28 cover" title="2024 04 28 cover" src="/static/15bd9a552317ca7f1a7c2f5fb6bcf305/a331c/2024-04-28-cover.png" srcset="/static/15bd9a552317ca7f1a7c2f5fb6bcf305/36ca5/2024-04-28-cover.png 200w, /static/15bd9a552317ca7f1a7c2f5fb6bcf305/a3397/2024-04-28-cover.png 400w, /static/15bd9a552317ca7f1a7c2f5fb6bcf305/a331c/2024-04-28-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://testcontainers.com/">Testcontainers</a> became a popular way of setting up dependencies for integration testing.</strong> They’re not ideal, as configuring them to run your tests efficiently is not as trivial as it’s glossed; I explained that in <a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a>. Still, undeniably, it can speed up the initial ramp-up phase and, if used wisely, can be a decent way to handle common testing dependencies.</p> <p>Testcontainers already provide the default configurations for tools like PostgreSQL, Kafka, MongoDB etc. Yet, sometimes you need to use something a bit less mainstream, for instance, <a href="https://developers.eventstore.com/">EventStoreDB</a> as your <a href="/en/event_stores_are_key_value_stores/">event store</a>. How to do it?</p> <p><strong>Luckily, test Containers provide a built-in way of handling such cases, providing a generic abstraction for that: <em>GenericContainer</em>.</strong> We can extend it to provide our test container. Let’s see how to do it using Typescript.</p> <p>We need to start by adding two classes, one representing a container image configuration and the other representing a started container instance.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> AbstractStartedContainer<span class="token punctuation">,</span> GenericContainer<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name">StartedTestContainer</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'testcontainers'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_IMAGE_NAME</span> <span class="token operator">=</span> <span class="token string">'eventstore/eventstore'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_IMAGE_TAG</span> <span class="token operator">=</span> <span class="token string">'23.10.1-bookworm-slim'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">EventStoreDBContainer</span> <span class="token keyword">extends</span> <span class="token class-name">GenericContainer</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span> image <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">EVENTSTOREDB_IMAGE_NAME</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">EVENTSTOREDB_IMAGE_TAG</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>StartedEventStoreDBContainer<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">StartedEventStoreDBContainer</span><span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">StartedEventStoreDBContainer</span> <span class="token keyword">extends</span> <span class="token class-name">AbstractStartedContainer</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span>container<span class="token operator">:</span> StartedTestContainer<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>container<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">getConnectionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">esdb://</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getHost</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getMappedPort</span><span class="token punctuation">(</span><span class="token number">2113</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?tls=false</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>And that’s the bare minimum that we need to have. The container definition takes the default LTS EventStoreDB image, allowing us to provide the other one. We’re also exposing the EventStoreDB connection string. It’s important as Testcontainers run containers on random ports to help in test isolation.</p> <p>We could set it up as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> container<span class="token operator">:</span> StartedEventStoreDBContainer <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">EventStoreDBContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> EventStoreDBClient<span class="token punctuation">.</span><span class="token function">connectionString</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getConnectionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span></code></pre></div> <p><strong>That’s some aid, but well, if Testcontainers are to reduce the cognitive load by providing a default container setup, then we could do better than that.</strong></p> <p>We could set up default recommended options for testing. What could they be? For instance, EventStoreDB is secure by default. You should run the setup to configure certificates, etc. It’s also recommended that a cluster with at least three nodes on production be configured to get proper resiliency. All of that is, of course, important, and running tests against the production setup is a must at some point (read more in <a href="/en/i_tested_on_production/">I tested it on production, and I’m not ashamed of it</a>). Still, for most of the integration tests touching common features (like appending events and (subscribing to notifications about them](/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/)), running an insecure single cluster should be more than enough. We could even disable file storage and use an in-memory setup to enable the reusing of the container between multiple tests. All of those are performance optimisations and tradeoffs, but we need to be sure that our test suite is not only giving proper results but also running fast. Tests on real production databases can be covered by <a href="/en/i_tested_on_production/">synthetic tests</a>.</p> <p>Ok, then, how to do it? Let’s define some default options like default EventStoreDB ports to expose:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_PORT</span> <span class="token operator">=</span> <span class="token number">2113</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_TCP_PORT</span> <span class="token operator">=</span> <span class="token number">1113</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_TCP_PORTS</span> <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token constant">EVENTSTOREDB_TCP_PORT</span><span class="token punctuation">,</span> <span class="token constant">EVENTSTOREDB_PORT</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre></div> <p>Then, defining the default image setup.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_IMAGE_NAME</span> <span class="token operator">=</span> <span class="token string">'eventstore/eventstore'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_IMAGE_TAG</span> <span class="token operator">=</span> <span class="token string">'23.10.1-bookworm-slim'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_ARM64_IMAGE_TAG</span> <span class="token operator">=</span> <span class="token string">'23.10.1-alpha-arm64v8'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">EVENTSTOREDB_DEFAULT_IMAGE</span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">EVENTSTOREDB_IMAGE_NAME</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>process<span class="token punctuation">.</span>arch <span class="token operator">!==</span> <span class="token string">'arm64'</span> <span class="token operator">?</span> <span class="token constant">EVENTSTOREDB_IMAGE_TAG</span> <span class="token operator">:</span> <span class="token constant">EVENTSTOREDB_ARM64_IMAGE_TAG</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span></code></pre></div> <p>And yes, we’re also giving help to the Mac M1 users, as the ARM64 image is not included in the default tag. It’s still in alpha, so why not automate it?</p> <p>Now, let’s define the explained earlier options:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">EventStoreDBContainerOptions</span> <span class="token operator">=</span> <span class="token punctuation">{</span> disableProjections<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> isSecure<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> useFileStorage<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> withoutReuse<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> defaultEventStoreDBContainerOptions<span class="token operator">:</span> EventStoreDBContainerOptions <span class="token operator">=</span> <span class="token punctuation">{</span> disableProjections<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> isSecure<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> useFileStorage<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> withoutReuse<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>As you see, we’re making them customisable, as we’d like people to enable the full setup. It’s also a good practice to have all default options named in a way so that the default can be assumed as <em>false</em>. That reduces the cognitive load.</p> <p>Having that, we can update our base container definition:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> AbstractStartedContainer<span class="token punctuation">,</span> GenericContainer<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'testcontainers'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> Environment <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'testcontainers/build/types'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">EventStoreDBContainer</span> <span class="token keyword">extends</span> <span class="token class-name">GenericContainer</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> tcpPorts <span class="token operator">=</span> <span class="token constant">EVENTSTOREDB_TCP_PORTS</span><span class="token punctuation">;</span> <span class="token function">constructor</span><span class="token punctuation">(</span> image <span class="token operator">=</span> <span class="token constant">EVENTSTOREDB_DEFAULT_IMAGE</span><span class="token punctuation">,</span> options<span class="token operator">:</span> EventStoreDBContainerOptions <span class="token operator">=</span> defaultEventStoreDBContainerOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>image<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> environment<span class="token operator">:</span> Environment <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span><span class="token punctuation">(</span><span class="token operator">!</span>options<span class="token punctuation">.</span>disableProjections <span class="token operator">?</span> <span class="token punctuation">{</span> <span class="token constant">EVENTSTORE_RUN_PROJECTIONS</span><span class="token operator">:</span> <span class="token string">'ALL'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">(</span><span class="token operator">!</span>options<span class="token punctuation">.</span>isSecure <span class="token operator">?</span> <span class="token punctuation">{</span> <span class="token constant">EVENTSTORE_INSECURE</span><span class="token operator">:</span> <span class="token string">'true'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>useFileStorage <span class="token operator">?</span> <span class="token punctuation">{</span> <span class="token constant">EVENTSTORE_MEM_DB</span><span class="token operator">:</span> <span class="token string">'false'</span><span class="token punctuation">,</span> <span class="token constant">EVENTSTORE_DB</span><span class="token operator">:</span> <span class="token string">'/data/integration-tests'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token constant">EVENTSTORE_CLUSTER_SIZE</span><span class="token operator">:</span> <span class="token string">'1'</span><span class="token punctuation">,</span> <span class="token constant">EVENTSTORE_START_STANDARD_PROJECTIONS</span><span class="token operator">:</span> <span class="token string">'true'</span><span class="token punctuation">,</span> <span class="token constant">EVENTSTORE_EXT_TCP_PORT</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">EVENTSTOREDB_TCP_PORT</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token constant">EVENTSTORE_HTTP_PORT</span><span class="token operator">:</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token constant">EVENTSTOREDB_PORT</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token constant">EVENTSTORE_ENABLE_EXTERNAL_TCP</span><span class="token operator">:</span> <span class="token string">'true'</span><span class="token punctuation">,</span> <span class="token constant">EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP</span><span class="token operator">:</span> <span class="token string">'true'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">withEnvironment</span><span class="token punctuation">(</span>environment<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withExposedPorts</span><span class="token punctuation">(</span><span class="token operator">...</span><span class="token keyword">this</span><span class="token punctuation">.</span>tcpPorts<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>options<span class="token punctuation">.</span>withoutReuse<span class="token punctuation">)</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">withReuse</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>StartedEventStoreDBContainer<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">StartedEventStoreDBContainer</span><span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, we’re setting up the default Environment settings, exposing ports, and marking our container as reusable by default (by calling <em>this.withReuse()</em>). Thanks to that, Testcontainers will not start a new container if a Testcontainers-managed container with the same configuration is already running. That cuts the cold-start time. Yet, you need to ensure that the test data you’re adding won’t collide with other tests, so you better be careful.</p> <p>You can still override the environment settings by calling <a href="https://node.testcontainers.org/features/containers/#with-environment-variables">withEnvironment method</a>.</p> <p>Having that, let’s also extend the started container by adding <em>getClient</em> method that’ll return already setup EventStoreDB client for your container.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> EventStoreDBClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@eventstore/db-client'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> AbstractStartedContainer<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name">StartedTestContainer</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'testcontainers'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">StartedEventStoreDBContainer</span> <span class="token keyword">extends</span> <span class="token class-name">AbstractStartedContainer</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span>container<span class="token operator">:</span> StartedTestContainer<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>container<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">getConnectionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">esdb://</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getHost</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getMappedPort</span><span class="token punctuation">(</span><span class="token number">2113</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">?tls=false</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">getClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> EventStoreDBClient <span class="token punctuation">{</span> <span class="token keyword">return</span> EventStoreDBClient<span class="token punctuation">.</span><span class="token function">connectionString</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">getConnectionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>You can test if all is working fine through the test:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> jsonEvent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@eventstore/db-client'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> assert <span class="token keyword">from</span> <span class="token string">'node:assert/strict'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> randomUUID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:crypto'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> after<span class="token punctuation">,</span> beforeEach<span class="token punctuation">,</span> describe<span class="token punctuation">,</span> it <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:test'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> EventStoreDBContainer<span class="token punctuation">,</span> StartedEventStoreDBContainer<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./eventStoreDBContainer'</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'EventStoreDBContainer'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> container<span class="token operator">:</span> StartedEventStoreDBContainer<span class="token punctuation">;</span> <span class="token function">beforeEach</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> container <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">EventStoreDBContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should connect to EventStoreDB and append new event'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> client <span class="token operator">=</span> container<span class="token punctuation">.</span><span class="token function">getClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">appendToStream</span><span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">test-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token function">jsonEvent</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'test-event'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> test<span class="token operator">:</span> <span class="token string">'test'</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> assert<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>success<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">after</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> container<span class="token punctuation">.</span><span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You can use the same pattern for any other tool with container image. Also you can define custom images in the similar way, if you’re using C#, Java, Go, or other supported by Testcontainers developer environment.</p> <p>Still, if you’re using Node.js, and you’d like to use EventStoreDB Testcontainer, then hey, you can just install Emmett Testcontainers package:</p> <div class="gatsby-highlight" data-language="shell"><pre class="language-shell"><code class="language-shell"><span class="token function">npm</span> <span class="token function">add</span> @event-driven-io/@event-driven-io/emmett-testcontainers</code></pre></div> <p>And use it as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> EventStoreDBContainer<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name">StartedEventStoreDBContainer</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-testcontainers'</span><span class="token punctuation">;</span> <span class="token keyword">let</span> esdbContainer<span class="token operator">:</span> StartedEventStoreDBContainer<span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'ShoppingCart E2E'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Set up a container before all tests</span> <span class="token function">before</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> esdbContainer <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">EventStoreDBContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Stop container once we finished testing</span> <span class="token function">after</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> esdbContainer<span class="token punctuation">.</span><span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...) Tests will go here</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Read also more in <a href="/en/testing_event_sourcing_emmett_edition/">Testing Event Sourcing, Emmett edition</a>.</p> <p>Check also my other articles on DevOps and Continuous Integration:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/docker_compose_profiles/">Docker Compose Profiles, one the most useful and underrated features</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/marten_and_docker">How to create a Docker image for the Marten application</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Mocking the native Node.js Test Runner]]>https://event-driven.io/en/mocking_nodejs_native_test_runner/https://event-driven.io/en/mocking_nodejs_native_test_runner/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2e81ac57bcc28bf5ca5dce91b648413c/a331c/2024-04-17-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAAB7UlEQVQ4y5WTS2tTURSFO1EcWwdFUP+AgkNLRyKCIkitXAcl1iZSgwTbiQ7EYh5qbjBR6TtGa4sY8EkHTgTTVJKaDkzpsEl7qVXTaqEJQW+TpjfJJ3mSxN6oBw77sPdinbXPXqeBmpXNZquiWk0N17AdoHROpVLYrCKnTpzE5/Pnc+l0WhVfJqxVkMlk8vHVi+doWk/jdg7z9vXLMmEt/g+FlUVla4vNZLIMmHCNMOV256gKhIpCQpbzUVVhZeL7twhrkRXkn7+YDc5x91oPYzeuMjU5yUJYIh6LsbaySkLeqE9YSn6RJL5KEu/fedBqtBguXcbSe5Ouzi4e2Pvwf/CxHA6zmUioD6Xy3X4shZiZeIap14Lugo6hvhEeOZ/QbejBYryDQ3TgGXWQ3oj/RWGRcD3ymY+jVq53X0FoE+i/N0D//UH0F/WYjbexG014h431CSul56wyMyYy/WYcTft57KID56ALraYTr9fH8lwAv8tCNqPUb7l0U24n4+ssLS5wtvUMVrMV58BDdB06AtOBPFaOR+sbu8qLxdaj0RjtgoDtlsj446cY9AaCn4IFzP/4sDScUGiew4cOcvzoMc61CbQcacbr8RS8qij/pjBXVIqGtYkiu3bsZG9TEwf27WfP7kbMJlPV99tu/QYStKHRIs4JdgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 04 17 cover" title="2024 04 17 cover" src="/static/2e81ac57bcc28bf5ca5dce91b648413c/a331c/2024-04-17-cover.png" srcset="/static/2e81ac57bcc28bf5ca5dce91b648413c/36ca5/2024-04-17-cover.png 200w, /static/2e81ac57bcc28bf5ca5dce91b648413c/a3397/2024-04-17-cover.png 400w, /static/2e81ac57bcc28bf5ca5dce91b648413c/a331c/2024-04-17-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Last week, we discussed an overused but applicable pattern: <a href="/en/inmemory_message_bus_in_typescript/">in-memory bus</a>.</strong> This time, we’ll continue with the leitmotif and talk about mocking. No, I won’t mock you; I will mock TypeScript code.</p> <p><strong>Node.js in version 18th added its own <a href="https://nodejs.org/api/test.html">native Test Runner</a>.</strong> Why did they do it? Jest looks like <a href="https://github.com/jestjs/jest/pull/11529#issuecomment-1027405616">abandonware</a> even after <a href="https://engineering.fb.com/2022/05/11/open-source/jest-openjs-foundation/">transferring to OpenJs Foundation</a>, issues are not getting closed, and it’s slow. <a href="https://vitest.dev/">Vitest</a> is a decent new alternative.</p> <p>Native Test Runner is also young but nicely integrated directly into Node.js and maintained by the core team. It’s lightweight, thanks to <a href="https://github.com/event-driven-io/emmett/pull/14">Thiago Valentim’s suggestion and contribution</a>. I started using it in <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a>. So far, so good; it’s really fast (each test file spawns as a Node.js subprocess); sometimes configuring it with TypeScript adds a bit of a headache, but which tool doesn’t?</p> <p><strong>That’s how we’re reaching mocking. What’s mocking?</strong> Nice, try, but I won’t go into that battle. Telling what’s right is a slippery slope, as you may get various answers from different people and arguments on the nitty gritty of the definition. Let me just <a href="https://martinfowler.com/articles/mocksArentStubs.html#TheDifferenceBetweenMocksAndStubs">quote Martin Fowler</a>:</p> <blockquote> <p><em>“(Mocks are) objects pre-programmed with expectations which form a specification of the calls they are expected to receive. (…) mocks insist upon behavior verification. The other doubles can, and usually do, use state verification.”</em></p> </blockquote> <p>Fowler shows the following example:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">class</span> <span class="token class-name">OrderInteractionTester</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">testOrderSendsMailIfUnfilled</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">Order</span> order <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Order</span><span class="token punctuation">(</span>TALISKER<span class="token punctuation">,</span> <span class="token number">51</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Mock</span> warehouse <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span><span class="token class-name">Warehouse</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Mock</span> mailer <span class="token operator">=</span> <span class="token function">mock</span><span class="token punctuation">(</span><span class="token class-name">MailService</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">)</span><span class="token punctuation">;</span> order<span class="token punctuation">.</span><span class="token function">setMailer</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">MailService</span><span class="token punctuation">)</span> mailer<span class="token punctuation">.</span><span class="token function">proxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> mailer<span class="token punctuation">.</span><span class="token function">expects</span><span class="token punctuation">(</span><span class="token function">once</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">method</span><span class="token punctuation">(</span><span class="token string">"send"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> warehouse<span class="token punctuation">.</span><span class="token function">expects</span><span class="token punctuation">(</span><span class="token function">once</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">method</span><span class="token punctuation">(</span><span class="token string">"hasInventory"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">withAnyArguments</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">will</span><span class="token punctuation">(</span><span class="token function">returnValue</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> order<span class="token punctuation">.</span><span class="token function">fill</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">Warehouse</span><span class="token punctuation">)</span> warehouse<span class="token punctuation">.</span><span class="token function">proxy</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>That’s also the reason why I don’t like Mocks;</strong> I disagree that testing if the method is called, by definition, focuses on behaviour. Too often, it just focuses on the mechanics and the way we implement our code instead of the real behaviour we’d like to test. Focusing on behaviour is really important to me, and I wrote about <a href="/en/behaviour_driven_design_is_not_about_tests/">here</a>. Still, let’s not go too far down this road, if you have more time, I encourage you to read a <a href="https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks">great take from James Shore about testing without mocks</a>.</p> <p>Still, it’s undeniably useful to sometimes mock the implementation of the real object or verify whether we actually called it. One way or another. Thanks to that, we can test our code or application without calling external services, doing a lot of IO operations, etc. Or ensure that dependency was called.</p> <p><strong>Also, it’s not always easy to see the results of our behaviour.</strong> Let’s take event publishing <a href="/en/inmemory_message_bus_in_typescript/">from the last article</a>. We have the following interface:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">interface</span> <span class="token class-name">EventsPublisher</span> <span class="token punctuation">{</span> <span class="token generic-function"><span class="token function">publish</span><span class="token generic class-name"><span class="token operator">&lt;</span>EventType <span class="token keyword">extends</span> Event <span class="token operator">=</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span>event<span class="token operator">:</span> EventType<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s fire and forget; the event is forwarded to the handlers. We could write a stub that’d be doing a simple implementation, but hey, we’d repeat much of what we did, as our <a href="/en/inmemory_message_bus_in_typescript/">implementation is already in-memory</a>. We could, of course, implement a stub for the handler and ensure that it was called. That wouldn’t be bad, but we could also just mock it.</p> <p>Let’s say that we have an <a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/cloud-design-patterns/acl.html">Anti-Corruption Layer</a> for payment gateway. We use it to reduce the scope of change in our system’s external API. When we make a payment, we also want to notify others that it was successfully made. The dummy implementation for that could look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">interface</span> <span class="token class-name">ExternalPaymentGateway</span> <span class="token punctuation">{</span> <span class="token function">makePayment</span><span class="token punctuation">(</span>amount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">PaymentMade</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span><span class="token string">'PaymentMade'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> amount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> paidAt<span class="token operator">:</span> Date <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">PaymentGatewayAcl</span> <span class="token operator">=</span> <span class="token punctuation">(</span> externalApi<span class="token operator">:</span> ExternalPaymentGateway<span class="token punctuation">,</span> eventPublisher<span class="token operator">:</span> EventsPublisher<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> makePayment<span class="token operator">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>amount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> externalApi<span class="token punctuation">.</span><span class="token function">makePayment</span><span class="token punctuation">(</span>amount<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> eventPublisher<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">publish</span><span class="token generic class-name"><span class="token operator">&lt;</span>PaymentMade<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'PaymentMade'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> amount<span class="token punctuation">,</span> paidAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Our ACL has two dependencies: an external payment API gateway and an Events Publisher.</p> <p>We also have a random factor, so the paid date is generated based on the current time. This makes our tests potentially non-deterministic, as from outside, we won’t know the exact value. We could pass the function that’d return us a new date and then mock this dependency, but we can also do it differently. We’ll get to that in a bit.</p> <p>Let’s try to set up our tests:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> assert <span class="token keyword">from</span> <span class="token string">'node:assert'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> describe<span class="token punctuation">,</span> it<span class="token punctuation">,</span> mock <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:test'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token keyword">type</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name">EventsPublisher</span><span class="token punctuation">,</span> getInMemoryMessageBus <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Payment Gateway Acl'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> eventsPublisher <span class="token operator">=</span> <span class="token function">getInMemoryMessageBus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> externalPaymentGatewayStub<span class="token operator">:</span> ExternalPaymentGateway <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token function-variable function">makePayment</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> sut <span class="token operator">=</span> <span class="token function">PaymentGatewayAcl</span><span class="token punctuation">(</span>externalPaymentGatewayStub<span class="token punctuation">,</span> eventsPublisher<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> mock<span class="token punctuation">.</span>timers<span class="token punctuation">.</span><span class="token function">enable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> apis<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'Date'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> now <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'publishes event when payment was made'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'We'</span>ll <span class="token keyword">get</span> here <span class="token keyword">in</span> a bit<span class="token operator">!</span>'<span class="token punctuation">)</span><span class="token punctuation">;</span> assert<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'does NOT publish event when payment failed'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'We'</span>ll <span class="token keyword">get</span> here <span class="token keyword">in</span> a bit<span class="token operator">!</span>'<span class="token punctuation">)</span><span class="token punctuation">;</span> assert<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The syntax is similar to the one you may know from Jest, Jasmine, and other testing frameworks. We’re starting by setting up our dependencies and system under test: <em>PaymentGatewayAcl</em>. We’re using the in-memory bus as it is and providing a stub of the external payment gateway with a <em>do-nothing-and-always-succeed</em> implementation.</p> <p>Node.js Native Test Runner provides basic abstractions for mocking. It is also neat that it allows mocking dates and timers like <em>setTimeout</em>.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> mock <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:test'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> mock<span class="token punctuation">.</span>timers<span class="token punctuation">.</span><span class="token function">enable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> apis<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'Date'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> now <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That means the Node.js test runtime will always inject the provided date instead of generating a new one, making our test non-deterministic. That’s the benefit of having a native test runner instead of a third-party solution. Read more options in <a href="https://nodejs.org/api/test.html#dates">documentation</a>.</p> <p>Let’s now fill the first test:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Payment Gateway Acl'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...) setup</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'publishes event when payment was made'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>test<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Given</span> <span class="token keyword">const</span> publishEvent <span class="token operator">=</span> test<span class="token punctuation">.</span>mock<span class="token punctuation">.</span><span class="token function">method</span><span class="token punctuation">(</span>eventsPublisher<span class="token punctuation">,</span> <span class="token string">'publish'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> amount <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span><span class="token punctuation">;</span> <span class="token comment">// When</span> <span class="token keyword">await</span> sut<span class="token punctuation">.</span><span class="token function">makePayment</span><span class="token punctuation">(</span>amount<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Then</span> <span class="token keyword">const</span> expectedEvent<span class="token operator">:</span> PaymentMade <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'PaymentMade'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> amount<span class="token punctuation">,</span> paidAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token function">verifyThat</span><span class="token punctuation">(</span>publishEvent<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">calledWith</span><span class="token punctuation">(</span>expectedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re telling the test runner to watch for our event publisher publish method.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> publishEvent <span class="token operator">=</span> test<span class="token punctuation">.</span>mock<span class="token punctuation">.</span><span class="token function">method</span><span class="token punctuation">(</span>eventsPublisher<span class="token punctuation">,</span> <span class="token string">'publish'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’ll need it to verify if the method was called with the expected parameters in the last line. We’re also using the <em>test.mock</em> from the test parameter, as this will ensure mocks will be set up only in the scope of the test. It is essential to have test isolation. We wouldn’t like to have calls in other tests interfere with our verification:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token function">verifyThat</span><span class="token punctuation">(</span>publishEvent<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">calledWith</span><span class="token punctuation">(</span>expectedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>And here we’re getting to the native test runner’s childhood issues.</strong> You can mock only a specific method, not the whole object. The API is pretty raw, and not all types are exposed out of the box.</p> <p>The <em>verifyThat</em> helper you saw was made by me. Let’s discuss how to provide such a simple wrapper.</p> <p>We need to define types for Mocked Function:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">AnyFunction</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">any</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Call</span> <span class="token operator">=</span> <span class="token punctuation">{</span> arguments<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> result<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> target<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">MockedFunction<span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> AnyFunction<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span> <span class="token operator">&amp;</span> <span class="token punctuation">{</span> mock<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">{</span> calls<span class="token operator">:</span> Call<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We started by defining <em>AnyFunction</em>, which (as the name suggests) represents functions with a set of arguments, essentially any functions. Defining it like that is essential; you’ll see it in a moment.</p> <p>Then, we’re adding typing for mocked function calls. It represents collections of calls that were made. Each call may have different arguments and results.</p> <p>Now we can define the <em>verifyThat</em> wrapper as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> verifyThat <span class="token operator">=</span> <span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> <span class="token class-name">AnyFunction</span><span class="token operator">></span><span class="token punctuation">(</span>fn<span class="token operator">:</span> MockedFunction<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token function-variable function">calledWith</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>args<span class="token operator">:</span> Parameters<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> assert<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span> fn<span class="token punctuation">.</span>mock<span class="token operator">?.</span>calls<span class="token punctuation">.</span>length <span class="token operator">!==</span> <span class="token keyword">undefined</span> <span class="token operator">&amp;&amp;</span> fn<span class="token punctuation">.</span>mock<span class="token punctuation">.</span>calls<span class="token punctuation">.</span>length <span class="token operator">>=</span> <span class="token number">1</span> <span class="token operator">&amp;&amp;</span> fn<span class="token punctuation">.</span>mock<span class="token punctuation">.</span>calls<span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token punctuation">(</span>call<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">deepEquals</span><span class="token punctuation">(</span>call<span class="token punctuation">.</span>arguments<span class="token punctuation">,</span> args<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">calledTimes</span><span class="token operator">:</span> <span class="token punctuation">(</span>times<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> assert<span class="token punctuation">.</span><span class="token function">equal</span><span class="token punctuation">(</span>fn<span class="token punctuation">.</span>mock<span class="token operator">?.</span>calls<span class="token operator">?.</span>length<span class="token punctuation">,</span> times<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">notCalled</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> assert<span class="token punctuation">.</span><span class="token function">equal</span><span class="token punctuation">(</span>fn<span class="token operator">?.</span>mock<span class="token operator">?.</span>calls<span class="token operator">?.</span>length<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">called</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> assert<span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span> fn<span class="token punctuation">.</span>mock<span class="token operator">?.</span>calls<span class="token punctuation">.</span>length <span class="token operator">!==</span> <span class="token keyword">undefined</span> <span class="token operator">&amp;&amp;</span> fn<span class="token punctuation">.</span>mock<span class="token punctuation">.</span>calls<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> deepEquals <span class="token operator">=</span> <span class="token punctuation">(</span>left<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span> right<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">boolean</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> assert<span class="token punctuation">.</span><span class="token function">deepEqual</span><span class="token punctuation">(</span>left<span class="token punctuation">,</span> right<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>The <em>verifyThat</em> is a builder function that returns an object with assertions for the passed function.</p> <p><strong>The most interesting one is <em>calledWith</em>. It uses <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype">Parameters</a> to build into TypeScript.</strong> It allows you to construct the array of arguments for the passed function. Moreover, it also respects the parameter types, even if they’re different! Thanks to that, the compiler won’t let you provide an incomplete number of parameters or use the wrong types. Sweet!</p> <p>I also added some examples of the other assertions you can pass there. I’m also using built-in assertions provided by Node.js natively. Let’s use one of them in the negative scenario test:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">void</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Payment Gateway Acl'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...) setup</span> <span class="token keyword">void</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'does NOT publish event when payment failed'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>test<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> publishEvent <span class="token operator">=</span> test<span class="token punctuation">.</span>mock<span class="token punctuation">.</span><span class="token function">method</span><span class="token punctuation">(</span>eventsPublisher<span class="token punctuation">,</span> <span class="token string">'publish'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> makePayment <span class="token operator">=</span> test<span class="token punctuation">.</span>mock<span class="token punctuation">.</span><span class="token function">method</span><span class="token punctuation">(</span> externalPaymentGatewayStub<span class="token punctuation">,</span> <span class="token string">'makePayment'</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> makePayment<span class="token punctuation">.</span>mock<span class="token punctuation">.</span><span class="token function">mockImplementation</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span><span class="token string">'You shall not pass!'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> amount <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> sut<span class="token punctuation">.</span><span class="token function">makePayment</span><span class="token punctuation">(</span>amount<span class="token punctuation">)</span><span class="token punctuation">;</span> assert<span class="token punctuation">.</span><span class="token function">fail</span><span class="token punctuation">(</span><span class="token string">'Expecting error!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">{</span> <span class="token function">verifyThat</span><span class="token punctuation">(</span>publishEvent<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notCalled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Native test runner also allows the replacement of the existing implementation.</strong> We’re doing that to simulate the failure of the payment gateway.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript">makePayment<span class="token punctuation">.</span>mock<span class="token punctuation">.</span><span class="token function">mockImplementation</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">reject</span><span class="token punctuation">(</span><span class="token string">'You shall not pass!'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>This will make our dependency fail; we can verify in the try/catch statement if our event publisher wasn’t indeed called by using humble wrapper:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token function">verifyThat</span><span class="token punctuation">(</span>publishEvent<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">notCalled</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And that’s it. I think I’ll also include this set of helpers for Emmett, so you can also benefit from it, but in any case, feel free to <em>steal</em> this code and have fun!</p> <p><strong>Yet, beware not to abuse mocks, and try to shape your code so you don’t need too much of them.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to build an in-memory Message Bus in TypeScript]]>https://event-driven.io/en/inmemory_message_bus_in_typescript/https://event-driven.io/en/inmemory_message_bus_in_typescript/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0614ac53e9a1e43c7bd0738558c80ac5/a331c/2024-04-12-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4jAAAuIwF4pT92AAAA6klEQVQoz2P4TwFgoL7mf//+kaH53969uw8ePwrRT9AIBjTb1qxc3jVx4vOnT/7+/UvQCeia96xfNae5ZsWcGbs2rn7+5BF+J6A4+++f3wc2rJzfVTutt3P+hM4dqxfduXH19+/fYCP+/v33l4DNb16+WDmheXpbTUdnZ3tN+dTOpg0rl96/dQPiC3w2Q/RfOXdqdm9Lf3tjS11FRnJCRUlha331xvVr799/8PPnT3xRBdF/+eypRX2d186d/vjp489fv3/8/Pnp8+eXL198+fIZORQZsEXy3+9fv/wAqyM/haGFM2awU5Q8Af0L4poVoVGDAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 04 12 cover" title="2024 04 12 cover" src="/static/0614ac53e9a1e43c7bd0738558c80ac5/a331c/2024-04-12-cover.png" srcset="/static/0614ac53e9a1e43c7bd0738558c80ac5/36ca5/2024-04-12-cover.png 200w, /static/0614ac53e9a1e43c7bd0738558c80ac5/a3397/2024-04-12-cover.png 400w, /static/0614ac53e9a1e43c7bd0738558c80ac5/a331c/2024-04-12-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I’m writing this article on Friday, and it’s about time to have some fun. As this is a programming blog, let’s have some fun coding.</strong> Let’s do an exercise designing a type-safe in-memory message bus in TypeScript!</p> <p>What’s a message bus? Let’s start from the beginning.</p> <p><strong>There are two ways of communicating: direct and indirect.</strong> For direct communication, we ask another component to perform a specific action and want to know if that happened. For indirect communication, we notify others that something has happened and let them decide what to do. In a nutshell, direct is represented by command, and indirect by event. Read more in <a href="/pl/whats_the_difference_between_event_and_command/">What’s the difference between a command and an event?</a>.</p> <p><strong>Typically, direct communication is assumed to be blocking and indirect non-blocking, but that’s a common practice, not a rule.</strong> Both types of communication can be blocking or non-blocking. In the real world, we may ask other people to do something and wait until they finish or assume that they will reply to us when they have done it. For indirect communication, even though we’re not interested in what will happen after we broadcast news, we’d like to know whether all interested parties took action.</p> <p>This may sound mind-boggling, but it’s not if we focus on modelling our communication as it’s happening in real business processes. I explained that with examples in <a href="/pl/how_to_have_fun_with_typescript_and_workflow/">How TypeScript can help in modelling business workflows</a>.</p> <p>What can we do with our messages, then? The bare minimum is:</p> <ul> <li><strong>sending:</strong> expecting it to be handled, but not returning a reason,</li> <li><strong>publishing:</strong> broadcasting information to all subscribers and waiting for them to process it.</li> <li><strong>scheduling:</strong> assuming that this message will be published later asynchronously, for instance, using <a href="/pl/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox Pattern</a>.</li> </ul> <p>There is more to that; Gregor Hohpe curated most of the <a href="https://www.enterpriseintegrationpatterns.com">Enterprise Integration Patterns</a> in his book and website. There are over 65 of them, so let’s start simple and expand in further articles.</p> <p>Let’s start with defining Command and Event types:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">Event<span class="token operator">&lt;</span> EventType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> EventData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> EventMetaData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> Flavour<span class="token operator">&lt;</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> EventType<span class="token punctuation">;</span> data<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>EventData<span class="token operator">></span><span class="token punctuation">;</span> metaData<span class="token operator">?</span><span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>EventMetaData<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token string">'Event'</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Command<span class="token operator">&lt;</span> CommandType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> CommandData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> CommandMetaData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> Flavour<span class="token operator">&lt;</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> CommandType<span class="token punctuation">;</span> data<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>CommandData<span class="token operator">></span><span class="token punctuation">;</span> metaData<span class="token operator">?</span><span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>CommandMetaData<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token string">'Command'</span> <span class="token operator">></span><span class="token punctuation">;</span> </code></pre></div> <p>They both have the <em>type</em> representing their name, data-specific information this message gathers, and optional metadata. As the structure is the same, they’re both <em>flavoured</em> to let the TypeScript compiler distinguish the differences between them. Read more details on that in <a href="/pl/how_to_have_fun_with_typescript_and_workflow/">other article</a>, in which I explained the details of this definition.</p> <p>Having them, let’s define how our sending and publishing definition will look like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">interface</span> <span class="token class-name">EventsPublisher</span> <span class="token punctuation">{</span> <span class="token generic-function"><span class="token function">publish</span><span class="token generic class-name"><span class="token operator">&lt;</span>EventType <span class="token keyword">extends</span> Event <span class="token operator">=</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span>event<span class="token operator">:</span> EventType<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">interface</span> <span class="token class-name">CommandSender</span> <span class="token punctuation">{</span> <span class="token generic-function"><span class="token function">send</span><span class="token generic class-name"><span class="token operator">&lt;</span>CommandType <span class="token keyword">extends</span> Command <span class="token operator">=</span> Command<span class="token operator">></span></span></span><span class="token punctuation">(</span> command<span class="token operator">:</span> CommandType<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>We’re making the intention specific by limiting publishing for events, as they are indirect broadcasts, and sending to allow only commands, as they represent direct communication to a single handler.</strong></p> <p>The schedule will look accordingly, but allow to schedule both types.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">ScheduleOptions</span> <span class="token operator">=</span> <span class="token punctuation">{</span> afterInMs<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> at<span class="token operator">:</span> Date <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">MessageScheduler<span class="token operator">&lt;</span>CommandOrEvent <span class="token keyword">extends</span> Command <span class="token operator">|</span> Event<span class="token operator">></span></span> <span class="token punctuation">{</span> <span class="token generic-function"><span class="token function">schedule</span><span class="token generic class-name"><span class="token operator">&lt;</span>MessageType <span class="token keyword">extends</span> CommandOrEvent<span class="token operator">></span></span></span><span class="token punctuation">(</span> message<span class="token operator">:</span> MessageType<span class="token punctuation">,</span> when<span class="token operator">?</span><span class="token operator">:</span> ScheduleOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Here, we assume that it’ll be forwarded in a separate process, and we’re not expecting to get any response either for command or from the event. If we want to reply to the status of the command, then we should do it by publishing the a follow up event with a new fact and handing it back. We also allow passing additional options informing when the message should be scheduled.</p> <p><strong>You may wonder why we aren’t putting all that into a single interface, and the reason is that we want to be precise about the intention.</strong> Typically, you’re either handling events or commands, and it’s better to have the option to use a narrowed interface that allows you to do only certain things. If we have the granular definitions, then we can also compose them into other interfaces, getting an all-in-one box. Here’s how you do it:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">interface</span> <span class="token class-name">EventBus</span> <span class="token keyword">extends</span> <span class="token class-name">EventsPublisher</span><span class="token punctuation">,</span> MessageScheduler<span class="token operator">&lt;</span>Event<span class="token operator">></span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">interface</span> <span class="token class-name">CommandBus</span> <span class="token keyword">extends</span> <span class="token class-name">CommandSender</span><span class="token punctuation">,</span> MessageScheduler<span class="token operator">&lt;</span>Command<span class="token operator">></span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">interface</span> <span class="token class-name">MessageBus</span> <span class="token keyword">extends</span> <span class="token class-name">CommandBus</span><span class="token punctuation">,</span> EventBus <span class="token punctuation">{</span> <span class="token generic-function"><span class="token function">schedule</span><span class="token generic class-name"><span class="token operator">&lt;</span>MessageType <span class="token keyword">extends</span> Command <span class="token operator">|</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span> message<span class="token operator">:</span> MessageType<span class="token punctuation">,</span> when<span class="token operator">?</span><span class="token operator">:</span> ScheduleOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>There you have it!</p> <p>Now that we know how to send, publish, and schedule messages, how do we handle them? We’ll need processors that allow registering functions with a specific handling. Let’s define them!</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">EventHandler<span class="token operator">&lt;</span>EventType <span class="token keyword">extends</span> Event <span class="token operator">=</span> Event<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">(</span> event<span class="token operator">:</span> EventType<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">|</span> <span class="token keyword">void</span><span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">EventProcessor</span> <span class="token punctuation">{</span> <span class="token generic-function"><span class="token function">subscribe</span><span class="token generic class-name"><span class="token operator">&lt;</span>EventType <span class="token keyword">extends</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span> eventHandler<span class="token operator">:</span> EventHandler<span class="token operator">&lt;</span>EventType<span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">...</span>eventTypes<span class="token operator">:</span> EventTypeOf<span class="token operator">&lt;</span>EventType<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">CommandHandler<span class="token operator">&lt;</span>CommandType <span class="token keyword">extends</span> Command <span class="token operator">=</span> Command<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> CommandType<span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">|</span> <span class="token keyword">void</span><span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">CommandProcessor</span> <span class="token punctuation">{</span> <span class="token generic-function"><span class="token function">handle</span><span class="token generic class-name"><span class="token operator">&lt;</span>CommandType <span class="token keyword">extends</span> Command<span class="token operator">></span></span></span><span class="token punctuation">(</span> commandHandler<span class="token operator">:</span> CommandHandler<span class="token operator">&lt;</span>CommandType<span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">...</span>commandTypes<span class="token operator">:</span> CommandTypeOf<span class="token operator">&lt;</span>CommandType<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We allow registering a single handler for the event and command handlers, as we may want to unify the handling of the commands or events grouped into <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types">Union Types</a>. For instance having:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GuestStayAccountEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> GuestCheckedIn <span class="token operator">|</span> ChargeRecorded <span class="token operator">|</span> PaymentRecorded <span class="token operator">|</span> GuestCheckedOut <span class="token operator">|</span> GuestCheckoutFailed<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">handleGuestStay</span> <span class="token operator">=</span> <span class="token punctuation">(</span>event<span class="token operator">:</span> GuestStayAccountEvent<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckedIn'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onCheckedIn</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ChargeRecorded'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onChargeRecorded</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckedOut'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onGuestCheckedOut</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">onGuestCheckoutFailed</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We can register it with:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript">eventProcessor<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span> handleGuestStay<span class="token punctuation">,</span> <span class="token string">'GuestCheckedIn'</span><span class="token punctuation">,</span> <span class="token string">'ChargeRecorded'</span><span class="token punctuation">,</span> <span class="token string">'GuestCheckedOut'</span><span class="token punctuation">,</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And accordingly with command handling.</p> <p>You may notice that I used two new types: <em>EventTypeOf</em> and <em>CommandTypeOf</em>. They’re used to having a strongly typed way of handling message types. They’re defined as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">CommandTypeOf<span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> Command<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span><span class="token punctuation">[</span><span class="token string">'type'</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">EventTypeOf<span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> Event<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">T</span><span class="token punctuation">[</span><span class="token string">'type'</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre></div> <p>It is a TypeScript trick that tells the compiler of the expected range of the message types. If we try to provide other values, then the compiler will show an error. Sweet!</p> <p>It would be even sweeter if we didn’t have to provide those string values but take them directly from the message type definition. Unfortunately, coding in TypeScript, we cannot have only good things. Those type annotations are only available at compile time. On runtime, there’s no trace of that. That’s the <em>“beauty”</em> of the dynamic runtime in JavaScript.</p> <p><strong>Cool, the last step before going finally to implementation is to implement a processor for scheduled messages:</strong></p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">ScheduledMessage</span> <span class="token operator">=</span> <span class="token punctuation">{</span> message<span class="token operator">:</span> Event <span class="token operator">|</span> Command<span class="token punctuation">;</span> options<span class="token operator">?</span><span class="token operator">:</span> ScheduleOptions<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">ScheduledMessageProcessor</span> <span class="token punctuation">{</span> <span class="token function">dequeue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> ScheduledMessage<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re exposing method that will dequeue the pending messages. We can take them and store them in <a href="/pl/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox</a> or forward to messaging queue.</p> <p>Now, we have our requirements, let’s proceed with the implementation. We’ll tackle that one by one, let’s start with the overall definition of the message bus setup:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">MessageHandler</span> <span class="token operator">=</span> EventHandler <span class="token operator">|</span> CommandHandler<span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">MessageProcessor</span> <span class="token operator">=</span> EventProcessor <span class="token operator">|</span> CommandProcessor<span class="token punctuation">;</span> <span class="token keyword">const</span> getInMemoryMessageBus <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token operator">&amp;</span> MessageBus <span class="token operator">&amp;</span> MessageProcessor <span class="token operator">&amp;</span> ScheduledMessageProcessor <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> allHandlers <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> MessageHandler<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">let</span> pendingMessages<span class="token operator">:</span> ScheduledMessage<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token comment">// (...) here will go the interfaces methods definition</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’ll take benefit of the <a href="/pl/structural_typing_in_type_script/">Structural Typing</a> and provide the one implementation to rule them all, as our command and event handler definitions are the same from the JavaScript runtime perspective. Only at compile time they differ, later on, they just take the message and run the message handling.</p> <p>We’ll also group all handlers in the same map that takes the message type and arrays of functions to process it. Event handlers can have multiple ones, for commands it’ll be always a single handler, as we won’t allow to register more than one.</p> <p>We’ll also have the unified pending messages collection, this will also guarantee the ordering on the producer side, which is quite important for handling workflows and building projections when subscribing to events.</p> <p>Let’s now show it one by one starting with registering event handler:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token generic-function"><span class="token function">subscribe</span><span class="token generic class-name"><span class="token operator">&lt;</span>EventType <span class="token keyword">extends</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span> eventHandler<span class="token operator">:</span> EventHandler<span class="token operator">&lt;</span>EventType<span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">...</span>eventTypes<span class="token operator">:</span> EventTypeOf<span class="token operator">&lt;</span>EventType<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> eventType <span class="token keyword">of</span> eventTypes<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>allHandlers<span class="token punctuation">.</span><span class="token function">has</span><span class="token punctuation">(</span>eventType<span class="token punctuation">)</span><span class="token punctuation">)</span> allHandlers<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> allHandlers<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token operator">...</span><span class="token punctuation">(</span>allHandlers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>eventType<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> eventHandler <span class="token keyword">as</span> MessageHandler<span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>For each event type, we’re just appending the handler to already existing set. A single event type can have multiple handlers (as event notification is a broadcast).</p> <p>Registering command handler looks accordingly, but there’s a slight change:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">return</span> <span class="token punctuation">{</span> handle<span class="token operator">:</span> <span class="token operator">&lt;</span>CommandType <span class="token keyword">extends</span> <span class="token class-name">Command</span><span class="token operator">></span><span class="token punctuation">(</span> commandHandler<span class="token operator">:</span> CommandHandler<span class="token operator">&lt;</span>CommandType<span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">...</span>commandTypes<span class="token operator">:</span> CommandTypeOf<span class="token operator">&lt;</span>CommandType<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> alreadyRegistered <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>allHandlers<span class="token punctuation">.</span><span class="token function">keys</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span>registered<span class="token punctuation">)</span> <span class="token operator">=></span> commandTypes<span class="token punctuation">.</span><span class="token function">includes</span><span class="token punctuation">(</span>registered<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>alreadyRegistered<span class="token punctuation">.</span>length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Cannot register handler for commands </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>alreadyRegistered<span class="token punctuation">.</span><span class="token function">join</span><span class="token punctuation">(</span><span class="token string">', '</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> as they're already registered!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> commandType <span class="token keyword">of</span> commandTypes<span class="token punctuation">)</span> <span class="token punctuation">{</span> allHandlers<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>commandType<span class="token punctuation">,</span> <span class="token punctuation">[</span>commandHandler <span class="token keyword">as</span> MessageHandler<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>As mentioned, we’re checking if there’s no handler already registered for the specific command type. If there’s then we’re throwing an error, otherwise just adding it to the handlers registration.</p> <p>Let’s now show how to publish an event:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> publish<span class="token operator">:</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span>EventType <span class="token keyword">extends</span> Event <span class="token operator">=</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span> event<span class="token operator">:</span> EventType<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> handlers <span class="token operator">=</span> allHandlers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> handler <span class="token keyword">of</span> handlers<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> eventHandler <span class="token operator">=</span> handler <span class="token keyword">as</span> EventHandler<span class="token operator">&lt;</span>EventType<span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">eventHandler</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’re just calling each registered handler sequentially. We could also use <em>Promise.all</em> and allow handling them in parallel, or allow both type of handling depending on how the event handler was registered.</p> <p>Sending command will look accordingly, but again we’re adding a validation.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> send<span class="token operator">:</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span>CommandType <span class="token keyword">extends</span> Command <span class="token operator">=</span> Command<span class="token operator">></span></span></span><span class="token punctuation">(</span> command<span class="token operator">:</span> CommandType<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> handlers <span class="token operator">=</span> allHandlers<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>type<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>handlers <span class="token operator">===</span> <span class="token keyword">undefined</span> <span class="token operator">||</span> handlers<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">EmmettError</span><span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">No handler registered for command </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>command<span class="token punctuation">.</span><span class="token keyword">type</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> commandHandler <span class="token operator">=</span> handlers<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span> <span class="token keyword">as</span> CommandHandler<span class="token operator">&lt;</span>CommandType<span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">commandHandler</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’re doublechecking if there’s an actual handler for command, because if it’s not then there’s no point for sending it, remember, it’s a direct communication, expecting command to be handled precisely once. We don’t need to check if there are more handlers, as we’re not allowing to register such.</p> <p>Scheduling is even simpler as we just need to put message into the pending items collection:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> schedule<span class="token operator">:</span> <span class="token operator">&lt;</span>MessageType <span class="token keyword">extends</span> <span class="token class-name">Command</span> <span class="token operator">|</span> Event<span class="token operator">></span><span class="token punctuation">(</span> message<span class="token operator">:</span> MessageType<span class="token punctuation">,</span> when<span class="token operator">?</span><span class="token operator">:</span> ScheduleOptions<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> pendingMessages <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>pendingMessages<span class="token punctuation">,</span> <span class="token punctuation">{</span> message<span class="token punctuation">,</span> options<span class="token operator">:</span> when <span class="token punctuation">}</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’ll dequeue it later on while processing those scheduled message. We’ll do it simply as that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> dequeue<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> ScheduledMessage<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> pending <span class="token operator">=</span> pendingMessages<span class="token punctuation">;</span> pendingMessages <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> pending<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>The good thing is that we can assign such object to any of the interfaces we defined above. E.g.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> eventPublisher<span class="token operator">:</span> EventPublisher <span class="token operator">=</span> <span class="token function">getInMemoryMessageBus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>or</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> commandBus<span class="token operator">:</span> CommandBus <span class="token operator">=</span> <span class="token function">getInMemoryMessageBus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It’ll respect the proper types. It’s a good starting point, we can separate handling when they’ll become too different. Our interfaces are already simple enough that they should be stable, allowing us to expand the implementation once we need it (e.g. adding stuff like retry policy, telemetry, middlewares, etc.).</p> <p>Do you want to use it in your project? No worries, I got you covered, <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a> already supports that!</p> <p>Just run</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">install</span> @event-driven-io/emmett</code></pre></div> <p>And you can use all those types out of the box, e.g.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getInMemoryMessageBus <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> messageBus <span class="token operator">=</span> <span class="token function">getInMemoryMessageBus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And you can use all those types out of the box, e.g.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getInMemoryMessageBus <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> messageBus <span class="token operator">=</span> <span class="token function">getInMemoryMessageBus</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then use it as for event subscription:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"> messageBus<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span> handleGuestStay<span class="token punctuation">,</span> <span class="token string">'GuestCheckedIn'</span><span class="token punctuation">,</span> <span class="token string">'ChargeRecorded'</span><span class="token punctuation">,</span> <span class="token string">'GuestCheckedOut'</span><span class="token punctuation">,</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>and for event publishing:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> event<span class="token operator">:</span>GuestCheckedOut <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GuestCheckedOut'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token string">'r9293'</span><span class="token punctuation">;</span> checkedOutAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">await</span> messageBus<span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Last, but not least, I’d like to end this article with a disclaimer.</p> <p><strong>Command bus can be overkill in cases where we have only a single entry point (e.g. API endpoint).</strong> In such cases, I suggest to have just explicit application code in the endpoint.</p> <p>For instance with <a href="https://event-driven-io.github.io/emmett/getting-started.html#webapi-definition">Emmett it’d look like that</a>:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> shoppingCartApi <span class="token operator">=</span> <span class="token punctuation">(</span> eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">,</span> <span class="token comment">// (...) other dependencies</span> <span class="token function-variable function">getUnitPrice</span><span class="token operator">:</span> <span class="token punctuation">(</span>productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token builtin">number</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WebApiSetup <span class="token operator">=></span> <span class="token punctuation">(</span>router<span class="token operator">:</span> Router<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/current/product-items'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> AddProductItemRequest<span class="token punctuation">,</span> response<span class="token operator">:</span> Response<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// 1. Translate request params to the command</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">getShoppingCartId</span><span class="token punctuation">(</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>clientId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> command<span class="token operator">:</span> AddProductItemToShoppingCart <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token function">assertPositiveNumber</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span><span class="token punctuation">,</span> unitPrice<span class="token operator">:</span> <span class="token keyword">await</span> <span class="token function">getUnitPrice</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 2. Handle command</span> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">addProductItem</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3. Send response status</span> response<span class="token punctuation">.</span><span class="token function">sendStatus</span><span class="token punctuation">(</span><span class="token number">204</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>That gives a proper developer experience, rather than just calling ev. As you understand your dependencies, you can go to the definition of business logic. A command bus requires much more jumping around the codebase as you hide all dependencies and handling details.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> shoppingCartApi <span class="token operator">=</span> <span class="token punctuation">(</span> commandSender<span class="token operator">:</span> CommandSender<span class="token punctuation">,</span> <span class="token function-variable function">getUnitPrice</span><span class="token operator">:</span> <span class="token punctuation">(</span>productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token builtin">number</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WebApiSetup <span class="token operator">=></span> <span class="token punctuation">(</span>router<span class="token operator">:</span> Router<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/current/product-items'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> AddProductItemRequest<span class="token punctuation">,</span> response<span class="token operator">:</span> Response<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// 1. Translate request params to the command</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">getShoppingCartId</span><span class="token punctuation">(</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>clientId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> command<span class="token operator">:</span> AddProductItemToShoppingCart <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token function">assertPositiveNumber</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span><span class="token punctuation">,</span> unitPrice<span class="token operator">:</span> <span class="token keyword">await</span> <span class="token function">getUnitPrice</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 2. Handle command</span> <span class="token keyword">await</span> commandSender<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3. Send response status</span> response<span class="token punctuation">.</span><span class="token function">sendStatus</span><span class="token punctuation">(</span><span class="token number">204</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>If I have more than one entry point, e.g., event (as it always can/should have more than one recipient) or command that may come from messaging tooling and API, then the command bus is useful, as it allows me to build common middleware.</p> <p>The downside of the message bus is that it can create another level of indirection, making it hard to understand where the handler is, what the impact of the change is, etc.</p> <p>Also, it doesn’t give proper delivery guarantees unless we wrap the whole processing in transactions, which can cause deadlocks and other types of complexity.</p> <p><strong>That’s why I’m not using it everywhere, but where I need it.</strong> I evolved from the message bus all the things. But it still can be useful, especially for event publishing, where, by nature, you don’t want to know how it’ll be handled as an event producer.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Event modelling anti-patterns explained]]>https://event-driven.io/en/anti-patterns/https://event-driven.io/en/anti-patterns/<p><strong>Have you heard about Passive Aggressive Events or CRUD sourcing? Or maybe about the Clickbait event?</strong></p> <p>If you don’t, you better check the talk I gave at Kafka Summit 2024. Knowing only best practices is one side of the coin. Knowing what NOT to do can be even more important.</p> <p><a href="https://www.confluent.io/events/kafka-summit-london-2024/event-modeling-anti-patterns/"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACqklEQVQozwGfAmD9AAi2xgirvwONpwdvjwZafAFPcAJmgwezxQe4yAe4yAu8zg6+zw2+zwy+zQy+zQ7B0A3C0QzA0Am9zQi9zQA3iHs6cmlfc2UwbGQsYVkIQT1QdXrJ5u294ee63OGkx8ynys6kx8usz9SlyMyixsqny8+qzdLD5uvB5esAKWxXLUE3RlBCI2RPIl1JCD8xZYB/+/j99PL109HTure4ycbJy8nM0s/Sy8jLxMHEycXH2NXX8/Dz8u/zAB9USCwsLT05NztQQydTQwcwJl15ePX6/rO4u3Z6fHJ2d7W4uvDx8+jq7Ofq7Ojs7+3z9+30+eju8ufs8AAYVEUaKyY2Oi8TNiwiODA/Vk2AmJPt7/JtbWq3uLiYlpNLPjPXvaqxjnube2u+ta7j5ebq6OXq6enr7fEAAEowFkMxRWZaR2lcPF9QACwYYX108vP2bl1Jh3xvcm9sUUE1zK2Yp3dqe009opCL4N7Z1K+A17yY6+3wAAAlC0tmWPHx8OLl47K9twAPAFhvZfX3/MHFxoKFhmZiX4p8bqmNhp10gqOEhYhvctna2+Tp6+Tp6uft8AAAIAsTMSBPYldXaF82TkAAEgBdcmj+///3+fz3+v7n5+rU0dDIw8fKxMrU09jCwsXq6u/49fz18ff18fcAAB0KABsIBR4OCyQUBR8NABQAMk1DlaSmnKahpK+roq2poqyqpK6roq2poaqoo62rpLatpdHEpdbIo9PGAAAZCAEaCQIaCgIbCgIaCQAaCQAVBAAOAAAOAAANAAANAAAMAAAKAAAJAAAIAAAHAAAXAABNIgCPVgCRWAAAFQYAFgYAFgcAFgcAFgYAFQYCFggEGAoEGQoEGAoEFwoDFgoDFgoDFAkDEwgDEggEFAoEHQcGYDIImFhFzTuM/lcQiwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2024 04 07 kafka" title="2024 04 07 kafka" src="/static/b9f6386f123628b338f86fd5303834b8/a331c/2024-04-07-kafka.png" srcset="/static/b9f6386f123628b338f86fd5303834b8/36ca5/2024-04-07-kafka.png 200w, /static/b9f6386f123628b338f86fd5303834b8/a3397/2024-04-07-kafka.png 400w, /static/b9f6386f123628b338f86fd5303834b8/a331c/2024-04-07-kafka.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p><strong>The talk also summarised my current article series about anti-patterns in event modelling. Here’s the full list:</strong></p> <ul> <li><a href="/en/state-obsession/">State Obsession</a>,</li> <li><a href="/en/property-sourcing/">Property Sourcing</a>,</li> <li><a href="/en/i_will_just_add_one_more_field/">I’ll just add one more field</a>.</li> <li><a href="/en/clickbait_event/">Clickbait event</a>,</li> <li><a href="/en/one_or_more_event_that_is_the_question/">Should you record multiple events from business logic?</a>,</li> <li><a href="/en/on_putting_stream_id_in_event_data/">Stream ids, event types prefixes and other event data you might not want to slice off</a>.</li> </ul> <p><strong>Check also more general considerations:</strong></p> <ul> <li><a href="/en/events_should_be_as_small_as_possible/">Events should be as small as possible, right?</a>,</li> <li><a href="/en/whats_the_difference_between_event_and_command/">What’s the difference between a command and an event?</a>,</li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a>,</li> <li><a href="/en/event_streaming_is_not_event_sourcing/">Event Streaming is not Event Sourcing!</a>,</li> <li><a href="/en/dont_let_event_driven_architecture_buzzwords_fool_you/">Don’t let Event-Driven Architecture buzzwords fool you</a>,</li> <li><a href="/en/how_to_design_software_architecture_pragmatically/">How to design software architecture pragmatically</a>,</li> <li><a href="/en/gdpr_in_event_driven_architecture/">How to deal with privacy and GDPR in Event-Driven systems</a>.</li> </ul> <p>During the session, I explained the specifics of event modelling, starting with bad practices and knowing why and how to avoid them. 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.</p> <p>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.</p> <p>The thin line is also between bad and good practices. And this thin line is: <em>context</em>.</p> <p><strong>If you’d like to avoid those mistakes, don’t hesitate to <a href="mailto:[email protected]">contact me!</a>.</strong> I’m here to help. Check my <a href="/en/training/">training</a> page. A workshop is the most effective way to jump-start.</p> <p>You can check <a href="https://www.linkedin.com/in/oskardudycz/">recommendations on my Linked.in profile</a> to see how other people liked working with me.</p> <p>Watch also my other talks:</p> <ul> <li><a href="https://www.youtube.com/watch?v=0pYmuk0-N_4">The Light and The Dark Side of the Event-Driven Design</a></li> <li><a href="https://www.youtube.com/watch?v=20zvAJAhqS0">Let’s build the worst Event Sourcing system!</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[I'm no longer Marten maintainer]]>https://event-driven.io/en/i_am_no_longer_marten_maintainer/https://event-driven.io/en/i_am_no_longer_marten_maintainer/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3bf55549d3381c9a8a179266bc781c4c/a331c/2024-04-29-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADm0lEQVQ4yyXR/VPTBQDH8e+/0U/dlXr90MMdRJfn5ZUpglkJ+RRy8syRA3Ts4Ts2BmxsgGxzGw8bgw323QaMwQLlyQF1pChiinRERBB54EWWGiSmx3X37qKfPr+97173EUZHB4h0t/NkY5OtrefMLMwTGY4yefM6xop8TNVnWVxeoclRhNshZ+XBQ7aebbO9/Q9P/nrBo41nrK6vc++HOZburyAMDUaJhP0sLC3z4Lc/uLOwxMDECLPzc5QqTlJWnkFsIoJR/TnminzWHj7mz82/2Xz6nI2nL7i3MM+duSmmZm8y9d23CD2REN5WJz+t/sLs3AwzN4aYGu/k7q0YivOpGKqKGZ8corE2h/q6bO6v/87ar4+ZmV9k8u41Br/uYuL2KANfddA/FkDw+jzUWxVcu9JG1Kbmqk1GzFbArUAFUl0hpqqz+LpNiJqj2Fx5TH//I4vLq8S+GaSzz0ZfrI3LYxLdg010DbgQ2iQP9foL2EoKKDl9mKyDCZx87w2KjsYRNmQz1l5OLHKRy14rHqOcG9NT/Lz2iOvTA/QO19IaqUTqqUKKXiTUb0ew28vJTfkUs6aAcMjMJ/vieOuVl0lK2EPGgdepTP8AS94RNJ+9Q/r+VzGqldyemebKiB1vRIetrZDWDi1Sr5lgnxWhxnSB0x99SGFGGsNjzdTVynj7td1oi7LISd1PYvwuEna9xOG43biq86mzlTA6HiXYbaLBr8TpV+KS1LhCpfi6KhGqjTIM5RpSkj/G4jiPMyTyRfFxjiUfoDAnCb0mBZ3yFPve3EPi3njqHUr6+iV8IT0uvxJ3h4jLr6K5Q0tLqAxBqzpDba3IidRDmBzZtETKcXeW0ftlE3JZMkptEuFeEzViJscS3+X9vfFYTKW4/SKNbUo8AS2eoIamgBpPsBRBVXycGkMWFrsMm7eYlh4drqCaaIcRTdEh1KpkfD4tVk0mfrcCKWpmaKSVULeRti49TQEV3k4dznY5noAGQadMw2ktwd9RQY0zD0trIeaGAnyeEhTnDqLVHsHbrKBel4X7kgxPSMtwzIs/bMATKN3htvy3kohbEhHKVGnUmXJxtMixtRZjcuZSacukM2ihWpuOTpeCu/EcjWUZuJxyGiUlwyM+QmHzDrlJUuEOiDT4S/4nl6nPYK0poMGjwOlXYXTkYrBlEm43ckmfS4X+BE5HDlbxFC57Ee6gyNVYO1KnkQavfOfh5oC4E20OavgXHKu+R24PF/sAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/3bf55549d3381c9a8a179266bc781c4c/a331c/2024-04-29-cover.png" srcset="/static/3bf55549d3381c9a8a179266bc781c4c/36ca5/2024-04-29-cover.png 200w, /static/3bf55549d3381c9a8a179266bc781c4c/a3397/2024-04-29-cover.png 400w, /static/3bf55549d3381c9a8a179266bc781c4c/a331c/2024-04-29-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Folks, I’d like to inform you that I’m no longer a Marten maintainer.</strong> As you know, sometimes in the project’s lifetime, there’s a moment when either you need to settle things or make hard decisions. Unfortunately, the latter occurred here.</p> <p><strong>I’m not happy that this is ending not as I hoped. Marten was a big part of my life: over 6 years as a contributor and over 5 years as a co-maintainer.</strong> Still, I’d like to thank Jeremy Miller and Babu Annamalai for our past collaboration. I think that we managed to build a unique tool. I’d also like to say big kudos to the Marten community. I always said that’s one of the big reasons that motivated me on this path.</p> <p>For various reasons, I already wasn’t active in the final push for the v7 version, which is a significant milestone. I think that shows that the team is motivated to keep it going.</p> <p><strong>I’m not leaving the Event Sourcing and Event-Driven space.</strong> I’m planning to continue what I was doing, so if you need any help from me, feel free to reach out to me.</p> <p>The closest goals are <a href="/en/speed_up_your_event_sourcing_journey_with_workshops/">the workshops on Techorama and DDD Europe</a>. Then, I’ll think about what’s next and how much I need to adjust my course.</p> <hr> <p>This blog is also a chronicle of my journey, I will finish it with a few stats and dates.</p> <p>As mentioned, I was Marten’s:</p> <ul> <li><strong>User for over 7 years.</strong> I’m not sure of the exact date, but I started my EventSourcing .NET samples to learn Event Sourcing and Marten. <a href="https://github.com/oskardudycz/EventSourcing.NetCore/commit/db211c70603dbb17a08d7b0ad53b5ac18468bb66">First commit was on 28th January 2017</a>, so it had to be a bit earlier than that.</li> <li><strong>Contributor for over 6 years.</strong> I <a href="https://github.com/JasperFx/marten/pull/841">opened my first pull request to Marten</a> (and in general to OSS ever) on the 6th of August 2017. We were missing asynchronous apply methods in projections, so I thought, “let’s try to do this OSS-thing”. It was merged on the 1st September 2017. Funnily enough, eventually, we didn’t use this change in our project as we were using it to load additional data while updating read models, which is not a great practice in general. But, well, we all need to start somewhere.</li> <li><strong>Maintainer for over 5 years</strong>. It happened <a href="https://jeremydmiller.com/2018/09/27/marten-3-0-is-released-and-introducing-the-new-core-team/">officially on Jeremy’s blog</a> on 27th September 2018. I was already active on our Gitter, so I am sending more Pull Requests. Jeremy invited Babu and me to join the core team, and we agreed.</li> </ul> <p>During that time, I’ve made:</p> <ul> <li>146 Pull Requests,</li> <li>592 Commits,</li> <li>214724 Additions,</li> <li>149,399 Removals</li> </ul> <p>Plus also work on <a href="https://github.com/JasperFx/weasel">Weasel</a>, spinoff for schema management:</p> <ul> <li>23 Pull Requests,</li> <li>90 Commits,</li> <li>12051 Additions,</li> <li>10010 Removals.</li> </ul> <p>Being 2nd most active contributor (both in this period and in general).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/843e81f335eab44c6685e41b42642ece/b7a58/2024-03-29-screen.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 99.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACyUlEQVQ4y5VUyW4VMRCcGxeSzO5lvM/2lryISHCAE1IQKBIH/v9nCnXP8gJEkTiU7GmXerpd1c60G+D7I1w6gPZhOPHexhnv8xY3hXgDEjYe4Icz0nyBdiOyWjpIk1BLj0Z5tF1EowMaFZDXmpO+hdtS4q5SzKV9VkmHtkuoVdghTA9pB0jT85noEq+v7yNaHfm7FBYZBaQbdxLB9WeE8fJfiNMDF8EJFSVc/0Kw6QQ/3L+O/h5+/DdGSamjrNERyo9oXQ9he27X9ScmEJnXcVnDtOJEeEA4XBh0Hg8PkGFAljcGKoxQw8AB6QfoMMPEA7SfYPojdJig/MzndjxCpRF2PEHHCXZYzikuXI9Mmci9541HKSOK1uKu7pA3C4rG4K7W8HGCNAPy1qNsyQHdwltBvKLpkH14fMTl42eU9h51uqCQDk159dpdKfHupsav5x/49OUrcnNGlc7Ia4m2Un/4kqyTUUV0j4Lk7wI6an+1ywbyZSkcr8wzER1dw1+8WnkytmchGg6SMCNqSrIlY2Jko9N5oxeeWq1GQ6HtwiMOJ3z5lxBGWLeYlmBsguoWw29m1uQGO8C7hBQSgk/8I66wkh5dmCHMknDqR5ymeW8n9TNCGFDryMqrtSIahhAndDbBuAQT5tWHPGoDSulRyYCidciF5zurCNLzWdlaTrrt97hwKITj6miMs+enb3j6/hO36oAqHFG0Ck0pV+XW16ZUmGOECUdU9oRbIVFVEmUh1hepvapMqtGzQ0o3NNd0PzruDwV1QNUIVni9fPIuXYkOf/KEQ8Z22EXpof2Mdlc9LQ7QEdWq8hZjXvfCNqbnxCzKS5XJDlRJHxILQOo16qrylnCxTdytQxCafKgCq7epTGPo44wp9ZxQ+QnKDlxlFw57UlKZZp06IZ4lxWmW6VG8PrB+qUZHFKy650Sbgu1+t0v8erZ80/obGC4UGm3rPBkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="screen" title="screen" src="/static/843e81f335eab44c6685e41b42642ece/a331c/2024-03-29-screen.png" srcset="/static/843e81f335eab44c6685e41b42642ece/36ca5/2024-03-29-screen.png 200w, /static/843e81f335eab44c6685e41b42642ece/a3397/2024-03-29-screen.png 400w, /static/843e81f335eab44c6685e41b42642ece/a331c/2024-03-29-screen.png 800w, /static/843e81f335eab44c6685e41b42642ece/b7a58/2024-03-29-screen.png 924w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>If we exclude the last three months when I wasn’t very active code-wise, then even on 1st. Of course, those are just code output stats; they don’t show the quality or importance of contribution, just activity. Ultimately, they’re just fun/dumb statistics for a chronicle like this.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/33bb410b0f42ead4d37f15fe1272e9ed/b7a58/2024-03-29-screen-2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 99.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsTAAALEwEAmpwYAAACuklEQVQ4y41UyY7bMAz1fZY4trXasmTJTpxk0l6KuXaZQdEC/f/feQUpx80sGPRA0KaeKIrviYXtR/h0QB9nGJdA/2E8otY9bjYCd1v1gWm4YYYfT4j7M2w/oWh0D91FNNpDGA9pA4QNoHjZWNyW8kO7rzQ2tWEs+YIqkW1EY8JqqkvQboTuEq+pNrJ//3uAtAOEHbAVHQoK6H5aQWR9OiFMZ/jpzP5/zI8PXAwnNJTQDmtCF48MeNfSA/z0Nk5FUNsKKtX4CbJPUC7xdft0XE/NlWYfdosdyT4hzOdshNk9oOkCik3TQfsJZhyhw8jfbdijG2ZYv0OXDrBhBxv20GGCmw4wMXs77ODGvN6NB0gXUXR+ROt3KIVHpXJjN02LUmSj/7Jp0fYjpI0oZcaVF9xihNuKFsWvH9/w9fszNu0JzXBCKQ1krVetkSxuNhJfHh8xf35E7R7QjGdUqoOs9AtdknSKrXSsO0X0t4HLN4tcLkbrlepZGozrAtp3cI3xJGzPRIglaK4kZF2EItGTzkyAbC84InBCY+OrhCEnvIjUUAI3oXURKUQEH/m7XgS/CrqLCMMEtxwolsMlVVhrz6zyO6YgySbOcH3iZESa9VmnxDwloKS7NOK0z/sooQsTOj+hEPzURlTKo9IBtepRac89I6MDqTeVdHx1WqulQ6l6bFXG1QuOrPj99Iynn39wb2bU4YBSGIiVPcn+tjLYD1ThEbU74l5p1I3GltZKibtS/mOZWKOxQwwKetf0WuywDgq6AZ2sWAm5X4wjhq8GCuNUj4LlsDKVYP2e+yhWAhIfVi8sX2IX3Moy40Im5Zr6i2yGENG5XFHzimXafD1QqHKaqdxDAmb20gr2cY9dzCwrN/JmqrIN85V0EqvjIiPnJ2g30ZXdmwHLw1Ln3okllgfxy96Ktdd+9X8BzZ4Tmv7OD1QAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="screen" title="screen" src="/static/33bb410b0f42ead4d37f15fe1272e9ed/a331c/2024-03-29-screen-2.png" srcset="/static/33bb410b0f42ead4d37f15fe1272e9ed/36ca5/2024-03-29-screen-2.png 200w, /static/33bb410b0f42ead4d37f15fe1272e9ed/a3397/2024-03-29-screen-2.png 400w, /static/33bb410b0f42ead4d37f15fe1272e9ed/a331c/2024-03-29-screen-2.png 800w, /static/33bb410b0f42ead4d37f15fe1272e9ed/b7a58/2024-03-29-screen-2.png 924w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We’ve built the Discord community for 1235 members (at the time of writing).</p> <p>We joined as one of the first .NET Foundation in March 2020. And we left it as one of the first in <a href="https://github.com/JasperFx/marten/issues/2183">February 2022</a>.</p> <hr> <p><strong>I’m not happy about the ending, but I don’t regret the journey.</strong></p> <p>Something is ending, something is starting. Off we go to the next chapter.</p> <p>Cheers</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Testing Event Sourcing, Emmett edition]]>https://event-driven.io/en/testing_event_sourcing_emmett_edition/https://event-driven.io/en/testing_event_sourcing_emmett_edition/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e85c2584a937eaa1d342b4bd583e8092/4491b/2024-04-23-hexagon.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAEa0lEQVQ4yz2Tb0xTBxTFb4tzku3D5mKcy+IWoyzORKClyGtf35++IswtEhXFKGIYJst0OpcgzrmR8MEKi4tOpxHKpgImm4FpxZQiG0MotSgtBChCsQ0KhUktDtChpX1nKVt2kvvpJif3/s69BICmpqYUPr+fTKbjb5RXVCybmJ4mFBTEIS9PQUT0Yv16eiSK1MWydFGtptSVKwmSNN+L6UxSEv0vq9WqrKqqotraWtbj8TwfHh5+dvrkyXf/LCggZGYSCgvpeUbGwqDB8EqPXv/qicTEeIqPV4YFgZCRoUB+PiE7m35Uqf41BBA3MjJKVmvDzn5P32993e6zX7n7Xu5hmEOjPH+ll9W2OLVpg92sNujj2MlJUXgYFIQhB8NsfsBx9FgQFiA9nQIcRxtWrZo3pLudrg+GfUNnthedyKctZ5dVr3hnU5eWAYxGPNEJcKnSMJTCYpYRAVGCbJQQEoSZ8qSkhF9TUuguw8R1Mgw5GIbIbDa/ZbPZ5LsdTlhdnk92GLdtt61bNzstGhBJN4RDO/jIeAEXndjLR0MH+OiTj4SozEphOV3CGMd5aMmS+EOrV9NVjUZxLjmZqPry5cVXa2tzzHV10oqlry3uTE7sHsj8EH+s3zbXfjQFY15OfjFmwLNREU8fiXLQwcvBLB5RnRSOGA24z7JXsHPnPL5ja9cSnfB6yWI2E4iUboZpGRN4QG8Mdx1g0XWHkUe69HJ3C4N77Tr03dLiaUCUZ3yiPL6Hk6GXwlGjBA/LmpCQQNi0SUnWrKy4RiKq5/lMv56NcYuMi4J876hWjk4a5KcPRPS3anGvXYup+wLmxgxyX6tWnt4uys/04pxPz2KAYYpmy8piQxGdq66mKx0d1HP4S/pZrT7j1enQsJsLj3ICQsW8PD0uyDPjIv4aERAOGmT/ECs37FPhIc+EB1kdbqVoaghQ/G6zqb8rLV1Enx88qLxusWxxdDjVscSvaVJ6pkQBkIyzs3rD3MTH/NxkET8X+oKPhA7ykdHP9JG/N4gvBnktmpi00CKil253ug4PDgygpqYmiSorK9Vutxu37fbzaG2ji+vS3rdrNNMzoggYJUAvAVoJ0P1XrBEwGPGYE2YrjclbhTcp2WG/Xd7b24vi4uK3qaKiYrnT6fzhmMmkcra33+rs779kXvx62iDHnR/l+cqHAlc1InK/BESuPiBytoDINQZ4vnOIYbYuBxZcvVSe5fF4sn0+3+m8vF0LqampifP7/YGbjY117Q7Hk+sWy80YXGzcSNi8mZCTQ8jNJezfT/j6G8KFC0Qmk6Lt+HH6yWze3dZmR1nZt+/FcLndbgV5PJ5cr9eLlpaW/O9PnVr46ZEjyt0AYdeuBcjJUSI3V4k9exQoLSU0N89/Fjo6lNWtrbH/NzY3N98pKipaGgu4pKREQS6Xa43P57tWX1+/xm63040bNxTXLJb5Qy1Rqeh0YiJdUqvpukZDDampdDM1ldoSEqi8sJAqzWYKTkwo9u7bS/tjGwD0D1TXb6QWY8OBAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e85c2584a937eaa1d342b4bd583e8092/a331c/2024-04-23-hexagon.png" srcset="/static/e85c2584a937eaa1d342b4bd583e8092/36ca5/2024-04-23-hexagon.png 200w, /static/e85c2584a937eaa1d342b4bd583e8092/a3397/2024-04-23-hexagon.png 400w, /static/e85c2584a937eaa1d342b4bd583e8092/a331c/2024-04-23-hexagon.png 800w, /static/e85c2584a937eaa1d342b4bd583e8092/8537d/2024-04-23-hexagon.png 1200w, /static/e85c2584a937eaa1d342b4bd583e8092/1a152/2024-04-23-hexagon.png 1600w, /static/e85c2584a937eaa1d342b4bd583e8092/4491b/2024-04-23-hexagon.png 3444w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I’ve been going pretty down the rabbit hole in the last few years. What am I searching for?</strong> A way to deliver better software. And that’s, of course, a <em>“thank you for nothing”</em> type of answer. And also a joke, I’ll tell you what I mean by better. I mean software close to the business, fulfilling the requirements with expected quality and allowing fast iterations to get a quicker feedback loop.</p> <p><strong>To do that, we need to use proper design/modelling techniques and a tool that enables building synergy.</strong> Some say your code should scream, but I don’t like shouting. That’s enough if the code is explicit and sincere. Yet, too often, being explicit means a lot of manual work and boilerplate. That’s not superb developer experience; I wrote more about that in <a href="/en/stacking_the_bricks/">Stacking the bricks in the software development process</a>. And that’s the biggest source of beef around coding styles and architecture. Different people see the tipping point between boilerplate and generic in other places. Where do I see it? That’s a longer take, but let’s focus on one specific aspect today: quality and testing.</p> <p>I wrote already here a few times about testing:</p> <ul> <li><a href="/en/behaviour_driven_design_is_not_about_tests/">Behaviour-Driven Design is more than tests</a></li> <li><a href="/en/testing_event_sourcing/">Testing business logic in Event Sourcing, and beyond!</a></li> <li><a href="/en/ogooreck_sneaky_bdd_testing_framework/">Ogooreck, a sneaky testing library in BDD style</a></li> <li><a href="/en/writing_and_testing_business_logic_in_fsharp/">Writing and testing business logic in F#</a></li> <li><a href="/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/">Testing asynchronous processes with a little help from .NET Channels</a></li> <li><a href="/en/i_tested_on_production/">I tested it on production and I’m not ashamed of it</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> </ul> <p>Today, I’d like to show you the next iteration and my current state of the art, which resulted from that journey.</p> <p><strong>I’ll show you how the <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a> way to testing can help with your developer experience!</strong></p> <p>Before we go, let’s install the Emmett npm package. We’ll use it in next steps:</p> <div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">npm add @event-driven-io/emmett</code></pre></div> <h2 id="domain" style="position:relative;"><a href="#domain" aria-label="domain permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Domain</h2> <p>Let me use a similar Shopping Cart domain I explained in <a href="/en/type_script_node_Js_event_sourcing/">Straightforward Event Sourcing with TypeScript and NodeJS</a>. The business logic looks as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> EmmettError<span class="token punctuation">,</span> IllegalStateError<span class="token punctuation">,</span> sum<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> addProductItem <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> AddProductItemToShoppingCart<span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ProductItemAddedToShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'Closed'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart already closed'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> removeProductItem <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> RemoveProductItemFromShoppingCart<span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ProductItemRemovedFromShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Opened'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is not opened'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">const</span> currentQuantity <span class="token operator">=</span> state<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentQuantity <span class="token operator">&lt;</span> productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Not enough products'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> removedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> confirm <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> ConfirmShoppingCart<span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartConfirmed <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Opened'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is not opened'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> totalQuantityOfAllProductItems <span class="token operator">=</span> <span class="token function">sum</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>totalQuantityOfAllProductItems <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is empty'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> cancel <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> CancelShoppingCart<span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartCancelled <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Opened'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart is not opened'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartCancelled'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> canceledAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">decide</span> <span class="token operator">=</span> <span class="token punctuation">(</span>command<span class="token operator">:</span> ShoppingCartCommand<span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> type <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">addProductItem</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'RemoveProductItemFromShoppingCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">removeProductItem</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ConfirmShoppingCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">confirm</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'CancelShoppingCart'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">cancel</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> _notExistingCommandType<span class="token operator">:</span> <span class="token builtin">never</span> <span class="token operator">=</span> type<span class="token punctuation">;</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">EmmettError</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Unknown command type</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>As you can see, it’s a simple set of functions that evaluate business rules (based on the input command and current state) and return an event as a result.</p> <p><strong>Okay, but how do I test it? Wouldn’t it be good if your library provided you with the recommended (but not enforced) method out of the box?</strong></p> <p>If you think the answer is yes, you should like what you’ll see next.</p> <p>One of the biggest benefits of having repeatable patterns is testing, which Emmett helps to do out of the box. For Event Sourcing, the testing pattern looks like this:</p> <ul> <li><strong>GIVEN</strong> set of events recorded for the entity,</li> <li><strong>WHEN</strong> we run the command on the state built from events,</li> <li><strong>THEN</strong> we’re getting new event(s) as a result of business logic. Or the exception is thrown.</li> </ul> <p>Let’s see how you can apply this pattern in Emmett.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> DeciderSpecification <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> given <span class="token operator">=</span> DeciderSpecification<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span><span class="token punctuation">{</span> decide<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> initialState<span class="token operator">:</span> getInitialState<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'ShoppingCart'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When empty'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should add product item'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> now <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When opened'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should confirm'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> oldTime<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ConfirmShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> now <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When confirmed'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should not add products'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> oldTime<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> oldTime <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> now <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">thenThrows</span><span class="token punctuation">(</span> <span class="token punctuation">(</span>error<span class="token operator">:</span> Error<span class="token punctuation">)</span> <span class="token operator">=></span> error<span class="token punctuation">.</span>message <span class="token operator">===</span> <span class="token string">'Shopping Cart already closed'</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> getRandomProduct <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> PricedProductItem <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> unitPrice<span class="token operator">:</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> oldTime <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productItem <span class="token operator">=</span> <span class="token function">getRandomProduct</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We start by setting up a specification for our tests by passing:</p> <ul> <li><strong><a href="https://event-driven-io.github.io/emmett/getting-started.html#business-logic-and-decisions">decide</a></strong> - a function that groups our command handling into a single method,</li> <li><strong><a href="https://event-driven-io.github.io/emmett/getting-started.html#building-state-from-events">evolve</a></strong> - a function that is used to <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">build the current state from events</a>,</li> <li><strong><a href="https://event-driven-io.github.io/emmett/getting-started.html#building-state-from-events">getInitialState</a></strong> - the initial, empty state on which we apply events to get the current one.</li> </ul> <p><strong><em>DeciderSpecification</em> helps you follow the Given/When/Then pattern.</strong> It builds the state internally based on the events passed in the <em>Given</em> part. It’ll run the business logic provided in the <em>When</em> part and then test the result events or an error.</p> <p>It’s as simple as that! I know that using <em>simple</em> is dangerous, but I really think it’s harder to make it more straightforward. Or at least I haven’t found a better way yet.</p> <h2 id="webapi" style="position:relative;"><a href="#webapi" aria-label="webapi permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>WebApi</h2> <p>Let’s not stop here, as Emmett also helps you with integration and end-to-end testing. But before we go, let’s show you quickly how you could define Express.js WebApi with a little help from Emmett.</p> <p>We need to install the Emmett Express.js npm package.</p> <div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">npm add @event-driven-io/emmett-expressjs</code></pre></div> <p>Having that the WebApi setup would look like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token keyword">type</span> <span class="token class-name">WebApiSetup</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-expressjs'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> Router <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'express'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> evolve<span class="token punctuation">,</span> getInitialState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../shoppingCart'</span><span class="token punctuation">;</span> <span class="token comment">// Let's setup the command handler, we'll use it in endpoints</span> <span class="token keyword">const</span> handle <span class="token operator">=</span> <span class="token function">CommandHandler</span><span class="token punctuation">(</span>evolve<span class="token punctuation">,</span> getInitialState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> shoppingCartApi <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token comment">// external dependencies</span> eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">,</span> <span class="token function-variable function">getUnitPrice</span><span class="token operator">:</span> <span class="token punctuation">(</span>productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token builtin">number</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WebApiSetup <span class="token operator">=></span> <span class="token punctuation">(</span>router<span class="token operator">:</span> Router<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// We'll setup routes here</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>The API definition is a function that takes external dependencies and returns the Web API setup. We’re also setting upCommand Handler that will orchestrate loading and storing events in the event store <a href="https://event-driven-io.github.io/emmett/getting-started.html#command-handling">(see more in Emmett docs)</a>.</p> <p>We can use it in the full setup like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getInMemoryEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> getApplication<span class="token punctuation">,</span> startAPI <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-expressjs'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> Application <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'express'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> Server <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'http'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> <span class="token function">getInMemoryEventStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> <span class="token function">shoppingCartApi</span><span class="token punctuation">(</span> eventStore<span class="token punctuation">,</span> getUnitPrice<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> application<span class="token operator">:</span> Application <span class="token operator">=</span> <span class="token function">getApplication</span><span class="token punctuation">(</span><span class="token punctuation">{</span> apis<span class="token operator">:</span> <span class="token punctuation">[</span>shoppingCarts<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> server<span class="token operator">:</span> Server <span class="token operator">=</span> <span class="token function">startAPI</span><span class="token punctuation">(</span>application<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’ll use in-memory event store for now. Yes, it’s also provided out of the box!</p> <p>Let’s not keep the setup empty for too long and define our first endpoint!</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript">router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/current/product-items'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> AddProductItemRequest<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// 1. Translate request params to the command</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">getShoppingCartId</span><span class="token punctuation">(</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>clientId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> command<span class="token operator">:</span> AddProductItemToShoppingCart <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token function">assertPositiveNumber</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span><span class="token punctuation">,</span> unitPrice<span class="token operator">:</span> <span class="token keyword">await</span> <span class="token function">getUnitPrice</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 2. Handle command</span> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">addProductItem</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 3. Return response status</span> <span class="token keyword">return</span> <span class="token function">NoContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Again, simple stuff. We:</p> <ul> <li>Translate and request params to the command.</li> <li>Run command handler on top of business logic.</li> <li>Return the proper HTTP response.</li> </ul> <p>Of course, we could make it even crisper and automagically do the request mapping, more conventional-based status resolution, decorators, and fire-command-and-forget, but we won’t. Why?</p> <p><strong>I prefer composability over magical glue.</strong> I believe that a healthy amount of copy-paste won’t harm you. I target removability and segregation of the code and making things explicit that should be explicit.</p> <p><strong>Still, I won’t tell you how to live!</strong> If you want to add more, feel free to do it. I want to give you basic building blocks and recommendations so you can build on top of that!</p> <p>Let’s skip other endpoints to keep the focus on tests. They’ll look accordingly to the above one. You can find a complete definition in <a href="https://event-driven-io.github.io/emmett/getting-started.html#webapi-definition">Emmett docs</a>.</p> <h2 id="integration-testing" style="position:relative;"><a href="#integration-testing" aria-label="integration testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Integration Testing</h2> <p>Cool, we now have API up and running. We even tested our <a href="#unit-testing">domain logic with unit tests</a>. That’s great, but as you know, a lot can happen in the meantime. The request mapping or validation may fail; middlewares (like auth one) can say no. It’d be great to test it.</p> <p>There are many different shapes of Testing: <a href="https://martinfowler.com/articles/practical-test-pyramid.html">Pyramids</a>, <a href="https://engineering.atspotify.com/2018/01/testing-of-microservices/">Honeycombs</a>, <a href="https://kentcdodds.com/blog/the-testing-trophy-and-testing-classifications">Thropies</a> etc. All of them share the goal of having them reliable and fast. However, agreeing on where the compromise is and where to put the biggest effort is, of course, challenging to agree.</p> <p>No matter what your preference is, Emmett has got you covered.</p> <p><strong>Let’s say that you’re a fan of <a href="https://jmgarridopaz.github.io/content/hexagonalarchitecture.html">Hexagonal/Ports &#x26; Adapters Architecture</a> and you’d like to test the whole flow being able to replace dependencies (adapters) with in-memory implementations to have your tests running in-memory.</strong> Such approaches have tradeoffs. The pros are that they run smoothly, allow a fast feedback loop, and run tests continuously. The downside is that they don’t validate all integration scenarios with real tools. Don’t worry, we’ll cover that later!</p> <p>I heard that one picture could speak more than a thousand words, so let’s look at this one:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e85c2584a937eaa1d342b4bd583e8092/4491b/2024-04-23-hexagon.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAEa0lEQVQ4yz2Tb0xTBxTFb4tzku3D5mKcy+IWoyzORKClyGtf35++IswtEhXFKGIYJst0OpcgzrmR8MEKi4tOpxHKpgImm4FpxZQiG0MotSgtBChCsQ0KhUktDtChpX1nKVt2kvvpJif3/s69BICmpqYUPr+fTKbjb5RXVCybmJ4mFBTEIS9PQUT0Yv16eiSK1MWydFGtptSVKwmSNN+L6UxSEv0vq9WqrKqqotraWtbj8TwfHh5+dvrkyXf/LCggZGYSCgvpeUbGwqDB8EqPXv/qicTEeIqPV4YFgZCRoUB+PiE7m35Uqf41BBA3MjJKVmvDzn5P32993e6zX7n7Xu5hmEOjPH+ll9W2OLVpg92sNujj2MlJUXgYFIQhB8NsfsBx9FgQFiA9nQIcRxtWrZo3pLudrg+GfUNnthedyKctZ5dVr3hnU5eWAYxGPNEJcKnSMJTCYpYRAVGCbJQQEoSZ8qSkhF9TUuguw8R1Mgw5GIbIbDa/ZbPZ5LsdTlhdnk92GLdtt61bNzstGhBJN4RDO/jIeAEXndjLR0MH+OiTj4SozEphOV3CGMd5aMmS+EOrV9NVjUZxLjmZqPry5cVXa2tzzHV10oqlry3uTE7sHsj8EH+s3zbXfjQFY15OfjFmwLNREU8fiXLQwcvBLB5RnRSOGA24z7JXsHPnPL5ja9cSnfB6yWI2E4iUboZpGRN4QG8Mdx1g0XWHkUe69HJ3C4N77Tr03dLiaUCUZ3yiPL6Hk6GXwlGjBA/LmpCQQNi0SUnWrKy4RiKq5/lMv56NcYuMi4J876hWjk4a5KcPRPS3anGvXYup+wLmxgxyX6tWnt4uys/04pxPz2KAYYpmy8piQxGdq66mKx0d1HP4S/pZrT7j1enQsJsLj3ICQsW8PD0uyDPjIv4aERAOGmT/ECs37FPhIc+EB1kdbqVoaghQ/G6zqb8rLV1Enx88qLxusWxxdDjVscSvaVJ6pkQBkIyzs3rD3MTH/NxkET8X+oKPhA7ykdHP9JG/N4gvBnktmpi00CKil253ug4PDgygpqYmiSorK9Vutxu37fbzaG2ji+vS3rdrNNMzoggYJUAvAVoJ0P1XrBEwGPGYE2YrjclbhTcp2WG/Xd7b24vi4uK3qaKiYrnT6fzhmMmkcra33+rs779kXvx62iDHnR/l+cqHAlc1InK/BESuPiBytoDINQZ4vnOIYbYuBxZcvVSe5fF4sn0+3+m8vF0LqampifP7/YGbjY117Q7Hk+sWy80YXGzcSNi8mZCTQ8jNJezfT/j6G8KFC0Qmk6Lt+HH6yWze3dZmR1nZt+/FcLndbgV5PJ5cr9eLlpaW/O9PnVr46ZEjyt0AYdeuBcjJUSI3V4k9exQoLSU0N89/Fjo6lNWtrbH/NzY3N98pKipaGgu4pKREQS6Xa43P57tWX1+/xm63040bNxTXLJb5Qy1Rqeh0YiJdUqvpukZDDampdDM1ldoSEqi8sJAqzWYKTkwo9u7bS/tjGwD0D1TXb6QWY8OBAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="hexagon" title="hexagon" src="/static/e85c2584a937eaa1d342b4bd583e8092/a331c/2024-04-23-hexagon.png" srcset="/static/e85c2584a937eaa1d342b4bd583e8092/36ca5/2024-04-23-hexagon.png 200w, /static/e85c2584a937eaa1d342b4bd583e8092/a3397/2024-04-23-hexagon.png 400w, /static/e85c2584a937eaa1d342b4bd583e8092/a331c/2024-04-23-hexagon.png 800w, /static/e85c2584a937eaa1d342b4bd583e8092/8537d/2024-04-23-hexagon.png 1200w, /static/e85c2584a937eaa1d342b4bd583e8092/1a152/2024-04-23-hexagon.png 1600w, /static/e85c2584a937eaa1d342b4bd583e8092/4491b/2024-04-23-hexagon.png 3444w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>The picture shows the boundaries between the business logic and our application layer.</strong></p> <p>Our application layer is thin; it’s a vertical slice to which the entry point (port) is the WebApi endpoint. Inside it, we can do additional stuff like getting product prices and mapping requests with additional data added to the command. We handle commands in the business logic that return event(s). We’re storing them in the event store. This looks like that in the already known adding product to shopping cart code:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> handle <span class="token operator">=</span> <span class="token function">CommandHandler</span><span class="token punctuation">(</span>evolve<span class="token punctuation">,</span> getInitialState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">AddProductItemRequest</span> <span class="token operator">=</span> Request<span class="token operator">&lt;</span> Partial<span class="token operator">&lt;</span><span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token punctuation">,</span> Partial<span class="token operator">&lt;</span><span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span><span class="token operator">></span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token comment">////////////////////////////////////////////////////</span> <span class="token comment">// Web Api</span> <span class="token comment">////////////////////////////////////////////////////</span> <span class="token keyword">export</span> <span class="token keyword">const</span> shoppingCartApi <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token comment">// external dependencies</span> eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">,</span> <span class="token function-variable function">getUnitPrice</span><span class="token operator">:</span> <span class="token punctuation">(</span>productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token builtin">number</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WebApiSetup <span class="token operator">=></span> <span class="token punctuation">(</span>router<span class="token operator">:</span> Router<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">////////////////////////////////////////////////////</span> <span class="token comment">// Endpoint</span> <span class="token comment">////////////////////////////////////////////////////</span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/current/product-items'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> AddProductItemRequest<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">getShoppingCartId</span><span class="token punctuation">(</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>clientId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> command<span class="token operator">:</span> AddProductItemToShoppingCart <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token function">assertPositiveNumber</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span><span class="token punctuation">,</span> unitPrice<span class="token operator">:</span> <span class="token keyword">await</span> <span class="token function">getUnitPrice</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">(</span>state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">addProductItem</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">NoContent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...) other endpoints</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">////////////////////////////////////////////////////</span> <span class="token comment">// Business Logic</span> <span class="token comment">////////////////////////////////////////////////////</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">AddProductItemToShoppingCart</span> <span class="token operator">=</span> Command<span class="token operator">&lt;</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> addProductItem <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> AddProductItemToShoppingCart<span class="token punctuation">,</span> state<span class="token operator">:</span> ShoppingCart<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> ProductItemAddedToShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'Closed'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateError</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart already closed'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> metadata<span class="token operator">?.</span>now <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Our slice has 3 ports that one can plug in:</strong></p> <ol> <li><strong>WebApi endpoint</strong>, where the user can send a request (which will be translated to a command).</li> <li><strong><em>getUnitPrice</em> function</strong> that gets the product price. Depending on our design, it may represent a call to an external service (e.g. with HTTP), calling a database (e.g. read model) or just running some computation. We’re also using it as an input to the command.</li> <li><strong>Event store</strong>, from which we load events and store new facts.</li> </ol> <p>We already made those dependencies explicit, allowing us to replace the real ones in the tests. Also, as we know that we’re doing Event Sourcing then, why not take advantage of that and write our tests in the following style:</p> <ul> <li><strong>GIVEN</strong> set of events recorded for the entity,</li> <li><strong>WHEN</strong> we run the web API request,</li> <li><strong>THEN</strong> we’re getting new event(s) as a result of business logic. Or the error status is returned with Problem Details.</li> </ul> <p>Let’s start with defining our WebApi specification. We’re using <em>ApiSpecification</em> class from <em>@event-driven-io/emmett-expressjs</em>.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getInMemoryEventStore<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name">EventStore</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> ApiSpecification<span class="token punctuation">,</span> getApplication<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-expressjs'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> unitPrice <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> given <span class="token operator">=</span> ApiSpecification<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">for</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartEvent<span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token operator">=></span> <span class="token function">getInMemoryEventStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">getApplication</span><span class="token punctuation">(</span><span class="token punctuation">{</span> apis<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token function">shoppingCartApi</span><span class="token punctuation">(</span> eventStore<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>unitPrice<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> now<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re also using the same <em>getApplication</em> known from the previous steps. The only difference is that we replaced real dependencies with the in-memory ones. <em>ApiSpecification</em> uses internally <a href="https://www.npmjs.com/package/supertest">SuperTest package</a>. It allows straightforward testing of Express.js, e.g. it ensures that server starts in a random port and provides the helpers for building test requests, which we’ll use in our tests.</p> <p>Having it, we can define our tests as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> existingStream<span class="token punctuation">,</span> expectNewEvents<span class="token punctuation">,</span> expectResponse<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-expressjs'</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When opened with product item'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should confirm'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">existingStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> oldTime<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/confirm</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token function">expectResponse</span><span class="token punctuation">(</span><span class="token number">204</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">expectNewEvents</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The test follows the similar Given/When/Then pattern as our unit tests but uses HTTP request to trigger the command handling and uses additional helpers to set up and verify data:</p> <ul> <li><strong>exisitingStream</strong> - allows you to specify the stream id and events existing in the stream. You can set more than one stream if your command handling logic requires that,</li> <li><strong>expectResponse</strong> - verifies the HTTP response with expected criteria like status code. You can also check the response body, headers, etc. For expected errors you can use <strong>expectError</strong> accordingly.</li> <li><strong>expectEvents</strong> - ensures that new events are appended to the specific streams in the event store.</li> </ul> <p>Complete tests will look like this:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getInMemoryEventStore<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name">EventStore</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> ApiSpecification<span class="token punctuation">,</span> existingStream<span class="token punctuation">,</span> expectError<span class="token punctuation">,</span> expectNewEvents<span class="token punctuation">,</span> expectResponse<span class="token punctuation">,</span> getApplication<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-expressjs'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> randomUUID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:crypto'</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'ShoppingCart'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">let</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token function">beforeEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> clientId <span class="token operator">=</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> shoppingCartId <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">shopping_cart:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:current</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When empty'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should add product item'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request <span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/product-items</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token function">expectNewEvents</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When opened with product item'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should confirm'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">existingStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> oldTime<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/confirm</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token function">expectResponse</span><span class="token punctuation">(</span><span class="token number">204</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">expectNewEvents</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When confirmed'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should not add products'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">existingStream</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> oldTime<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> oldTime <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request <span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/product-items</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span> <span class="token function">expectError</span><span class="token punctuation">(</span><span class="token number">403</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> detail<span class="token operator">:</span> <span class="token string">'Shopping Cart already closed'</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token number">403</span><span class="token punctuation">,</span> title<span class="token operator">:</span> <span class="token string">'Forbidden'</span><span class="token punctuation">,</span> type<span class="token operator">:</span> <span class="token string">'about:blank'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> oldTime <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> unitPrice <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">;</span> <span class="token keyword">const</span> given <span class="token operator">=</span> ApiSpecification<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">for</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartEvent<span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token operator">=></span> <span class="token function">getInMemoryEventStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">getApplication</span><span class="token punctuation">(</span><span class="token punctuation">{</span> apis<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token function">shoppingCartApi</span><span class="token punctuation">(</span> eventStore<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>unitPrice<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> now<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> getRandomProduct <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> PricedProductItem <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> unitPrice<span class="token punctuation">,</span> quantity<span class="token operator">:</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productItem <span class="token operator">=</span> <span class="token function">getRandomProduct</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You can use those tests as complementary to the business logic (e.g., testing the most important scenarios), or you may even replace unit tests with them. As they’re in memory, they’re fast enough to be run continuously.</p> <p>You can also replace the in-memory store with the real one (e.g. <a href="https://developers.eventstore.com/">EventStoreDB</a>) and test your module in isolation from other modules. The choice is yours!</p> <p>Again, in Emmett, we don’t want to force you to anything but give you options and the recommended safe path.</p> <p>I encourage you to watch Martin Thwaites’ talk <a href="https://www.youtube.com/watch?v=prLRI3VEVq4">“Building Operable Software with TDD (but not the way you think)”</a>. It nicely explains why I can now shift the balance from unit testing to integration testing.</p> <h2 id="end-to-end-testing" style="position:relative;"><a href="#end-to-end-testing" aria-label="end to end testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>End-to-End Testing</h2> <p>You may say:</p> <blockquote> <p><em>Oskar, those tests are cool, but I’d like to either test business logic with unit tests or run my end-to-end tests treating the WebApi as a black box.</em></p> </blockquote> <p>And I answer: sure, why not! I also give you help with that.</p> <p>Let’s start by adding some flavour and use <a href="https://developers.eventstore.com/">EventStoreDB</a> this time. We need to install two more packages. One for adding implementation of the <a href="https://developers.eventstore.com/">EventStoreDB</a> event store:</p> <div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">npm add @event-driven-io/@event-driven-io/emmett-esdb</code></pre></div> <p>Now, we need to switch the in-memory implementation to <a href="https://developers.eventstore.com/">EventStoreDB</a> in WebApi setup. Updated will look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getEventStoreDBEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-esdb'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> EventStoreDBClient <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@eventstore/db-client'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> eventStoreDBClient <span class="token operator">=</span> EventStoreDBClient<span class="token punctuation">.</span><span class="token function">connectionString</span><span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">esdb://localhost:2113?tls=false</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> <span class="token function">getEventStoreDBEventStore</span><span class="token punctuation">(</span>eventStoreDBClient<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> <span class="token function">shoppingCartApi</span><span class="token punctuation">(</span> eventStore<span class="token punctuation">,</span> getUnitPrice<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It’s as simple as that; we’re injecting just a different implementation.</p> <p>As <a href="https://developers.eventstore.com/">EventStoreDB</a> is a real database, we need to set it up for our tests. The simplest option is to use a Docker container. You can do it in multiple ways, but the fastest can be using <a href="https://node.testcontainers.org/">TestContainers</a>. The library allows us to easily set up containers for our tests. It automatically randomise ports, helps in teardown etc. (read more <a href="/en/configure_ci_for_integration_tests/">here</a>, to see if it’s always a best option)</p> <p>Emmett provides the package with additional test containers like the one for <a href="https://developers.eventstore.com/">EventStoreDB</a>. You need to install:</p> <div class="gatsby-highlight" data-language="sh"><pre class="language-sh"><code class="language-sh">npm add @event-driven-io/@event-driven-io/emmett-testcontainers</code></pre></div> <p>:::</p> <p>Having that, we can set our test container with:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> EventStoreDBContainer<span class="token punctuation">,</span> <span class="token keyword">type</span> <span class="token class-name">StartedEventStoreDBContainer</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-testcontainers'</span><span class="token punctuation">;</span> <span class="token keyword">let</span> esdbContainer<span class="token operator">:</span> StartedEventStoreDBContainer<span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'ShoppingCart E2E'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// Set up a container before all tests</span> <span class="token function">before</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> esdbContainer <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">EventStoreDBContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Stop container once we finished testing</span> <span class="token function">after</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> esdbContainer<span class="token punctuation">.</span><span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...) Tests will go here</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And create our test specification using the <em>ApiE2ESpecification</em> type:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token keyword">type</span> <span class="token class-name">EventStore</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> getEventStoreDBEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-esdb'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> ApiE2ESpecification<span class="token punctuation">,</span> getApplication<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-expressjs'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> given <span class="token operator">=</span> ApiE2ESpecification<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token operator">=></span> <span class="token function">getEventStoreDBEventStore</span><span class="token punctuation">(</span>esdbContainer<span class="token punctuation">.</span><span class="token function">getClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">getApplication</span><span class="token punctuation">(</span><span class="token punctuation">{</span> apis<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token function">shoppingCartApi</span><span class="token punctuation">(</span> eventStore<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>unitPrice<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> now<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The test will look accordingly to the integration tests, with the distinction that we also use HTTP requests for the setup. We’re also checking only responses; we treat the WebApi as a black box.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> getEventStoreDBEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-esdb'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> expectResponse <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-expressjs'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> StartedEventStoreDBContainer <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-testcontainers'</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When opened with product item'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should confirm'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request <span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/product-items</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/confirm</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token function">expectResponse</span><span class="token punctuation">(</span><span class="token number">204</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Complete tests will look like this:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> <span class="token keyword">type</span> <span class="token class-name">EventStore</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> getEventStoreDBEventStore <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-esdb'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> ApiE2ESpecification<span class="token punctuation">,</span> expectResponse<span class="token punctuation">,</span> getApplication<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-expressjs'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> EventStoreDBContainer<span class="token punctuation">,</span> StartedEventStoreDBContainer<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett-testcontainers'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> randomUUID <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'node:crypto'</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'ShoppingCart E2E'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> unitPrice <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">;</span> <span class="token keyword">let</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">let</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">let</span> esdbContainer<span class="token operator">:</span> StartedEventStoreDBContainer<span class="token punctuation">;</span> <span class="token keyword">let</span> given<span class="token operator">:</span> ApiE2ESpecification<span class="token punctuation">;</span> <span class="token function">before</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> esdbContainer <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">EventStoreDBContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> given <span class="token operator">=</span> ApiE2ESpecification<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> EventStore <span class="token operator">=></span> <span class="token function">getEventStoreDBEventStore</span><span class="token punctuation">(</span>esdbContainer<span class="token punctuation">.</span><span class="token function">getClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>eventStore<span class="token operator">:</span> EventStore<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">getApplication</span><span class="token punctuation">(</span><span class="token punctuation">{</span> apis<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token function">shoppingCartApi</span><span class="token punctuation">(</span> eventStore<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>unitPrice<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> now<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">beforeEach</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> clientId <span class="token operator">=</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> shoppingCartId <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">shopping_cart:</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">:current</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">after</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> esdbContainer<span class="token punctuation">.</span><span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'When opened with product item'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should confirm'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request <span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/product-items</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/confirm</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token function">expectResponse</span><span class="token punctuation">(</span><span class="token number">204</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should return details'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request <span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current/product-items</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token operator">=></span> request<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">/clients/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>clientId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/shopping-carts/current</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token function">expectResponse</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> body<span class="token operator">:</span> <span class="token punctuation">{</span> clientId<span class="token punctuation">,</span> id<span class="token operator">:</span> shoppingCartId<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> quantity<span class="token operator">:</span> productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> productId<span class="token operator">:</span> productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Opened<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> getRandomProduct <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> PricedProductItem <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> unitPrice<span class="token operator">:</span> unitPrice<span class="token punctuation">,</span> quantity<span class="token operator">:</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">10</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productItem <span class="token operator">=</span> <span class="token function">getRandomProduct</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>KUDOS also goes to <a href="https://github.com/thiagomini">Thiago Valentim</a> for contribution and <a href="https://github.com/event-driven-io/emmett/pull/29">delivering E2E tests feature</a>.</p> <p>Check also the full <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett getting started guide</a> and see the <a href="https://github.com/event-driven-io/emmett/tree/main/samples/webApi/expressjs-with-esdb">full sample in Emmett repository</a>.</p> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p><strong>I really think that Event Sourcing, with its repeatable patterns and focus on business, can streamline development.</strong> Yet, learning it may be challenging. You need to learn both new approaches and tooling. That’s why I decided to publish Emmett to package safe defaults and my state-of-the-art. I don’t want to replace or compete with existing tooling but guide you through this journey.</p> <p><strong>See that tests are first-class citizens in Emmett.</strong> I think that’s a pretty rare thing. I want to make getting the trust in your code and yourself smoother while going through the Event Sourcing journey. The proportion between unit, integration and end-to-end tests is up to you. The most important part is that I’ve got you covered.</p> <p><strong>I’d be really curious about your thoughts on this approach. Feel free to comment here or <a href="https://discord.gg/fTpqUTMmVa">Join our Discord</a> and discuss it with me there!</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Join my Event Sourcing workshops at Techorama and DDD Europe and speed up your journey!]]>https://event-driven.io/en/speed_up_your_event_sourcing_journey_with_workshops/https://event-driven.io/en/speed_up_your_event_sourcing_journey_with_workshops/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ffb939951e5d2fc18eeb254743ff7184/c60e9/2024-03-17-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAAC/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAV4LQ+BV/8QAGRAAAgMBAAAAAAAAAAAAAAAAAQIAERIh/9oACAEBAAEFAlSOMqGFPxBmqn//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAbEAACAgMBAAAAAAAAAAAAAAAAEQEhEBIxcf/aAAgBAQAGPwLpOs2RYmW37j//xAAcEAEAAgIDAQAAAAAAAAAAAAABABEhMUFhcYH/2gAIAQEAAT8hehp7LV8LFsnU5srneJcKd0Aqf//aAAwDAQACAAMAAAAQZN//xAAWEQEBAQAAAAAAAAAAAAAAAAABADH/2gAIAQMBAT8QNkv/xAAWEQEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQIBAT8QxH//xAAcEAEAAwACAwAAAAAAAAAAAAABABEhQVFhkcH/2gAIAQEAAT8QtwVPTqpqqMTQB1CPBqFthpI0KqzWfZRqUU3DMO68Sp4n/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ffb939951e5d2fc18eeb254743ff7184/c60e9/2024-03-17-cover.jpg" srcset="/static/ffb939951e5d2fc18eeb254743ff7184/37402/2024-03-17-cover.jpg 200w, /static/ffb939951e5d2fc18eeb254743ff7184/4cda9/2024-03-17-cover.jpg 400w, /static/ffb939951e5d2fc18eeb254743ff7184/c60e9/2024-03-17-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Event Sourcing is a pattern that is quickly gaining popularity.</strong> Many companies see the advantages it brings, e.g. business focus and keeping all information about the business process. It can also be a great input to analytics, providing increased diagnostics and tracing. Nowadays, that’s essential for running a system on the scale.</p> <p>There are already decent materials teaching how to start the journey with Event Sourcing. I tried to cover a lot of aspects of it in this blog. So starting is easier, but it requires a ramp-up phase. Once you pass it, it’s not easy to get certainty that our design will work on production. Even when we get there, it’s worth revisiting lessons learned and discovering what we can improve in preparing for the upcoming challenges.</p> <p><strong>We can hit accidental complexity.</strong> That can be overwhelming. I learned that the hard way. I tackled those problems in my projects as:</p> <ul> <li>a developer and architect,</li> <li>consultant with my clients</li> <li>tooling author.</li> </ul> <p>I think that those versatile lessons could also be useful for you.</p> <p><strong>Do you want to speed up your journey?</strong> Do you like onsite conferences?</p> <p><strong>You can mash those two together, as I’ll be giving pre-conference workshops on my favourite conferences:</strong></p> <ul> <li><strong><a href="https://techorama.be/workshops/practical-introduction-to-event-sourcing?utm_source=event_driven_io">Techorama - 6th of May</a>,</strong></li> <li><strong><a href="https://2024.dddeurope.com/program/production-grade-event-sourcing-modelling-devops-process?utm_source=event_driven_io">DDD Europe - 27th-29th of May.</a></strong></li> </ul> <h2 id="practical-introduction-to-event-sourcing-at-techorama" style="position:relative;"><a href="#practical-introduction-to-event-sourcing-at-techorama" aria-label="practical introduction to event sourcing at techorama permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Practical Introduction to Event Sourcing at Techorama</h2> <p><strong>Date:</strong> <a href="https://techorama.be/workshops/practical-introduction-to-event-sourcing?utm_source=event_driven_io">6th of May</a>.</p> <p>The workshop will be hands-on and teach you how to use Event Sourcing, giving you solid foundations. You will understand after them:</p> <ul> <li>when and how to use it and what benefits it brings,</li> <li>how to reflect your business logic in the code using events,</li> <li>differences to the classical approach,</li> <li>different tools such as Marten and EventStoreDB, and the differences between them,</li> <li>how to use Event Sourcing on your system,</li> <li>challenges related to Event Sourcing and recommended solutions.</li> </ul> <p><strong>The general plan looks like this:</strong></p> <ol> <li>Introduction to Event Sourcing. Basic terminology (event, stream of events, command), differences from the classical approach.</li> <li>What is Event Sourcing, and how is it different from Event Streaming. Advantages and disadvantages.</li> <li>Write model and data consistency guarantees.</li> <li>Various ways of handling business logic: Aggregates, Command Handlers, functional approach.</li> <li>Projections and best practices for building a read model on the.</li> <li>Event Sourcing in the context of application architecture and integration with other approaches.</li> <li>Saga, Choreography, Process Manager, handling distributed processes.</li> <li>Good and bad practices in modelling and handling events.</li> <li>Event Sourcing on production, evolution, event versioning, etc.</li> </ol> <p>This should build a good foundation for your Event Sourcing journey. I won’t lie: you won’t be an expert after it, but you’ll be able to start applying it to your projects, learn what you don’t know, and have a plan for advancing it.</p> <p><strong>The requirements to join are simple:</strong></p> <ul> <li>understanding of the basic building blocks of the application design,</li> <li>experience in one of the languages and platforms: C#, Java, TypeScript (code exercises will be done using them),</li> <li>positive and open-minded attitude.</li> </ul> <p>Interested? <a href="https://techorama.be/workshops/practical-introduction-to-event-sourcing?utm_source=event_driven_io">Sign up here</a>.</p> <h2 id="production-grade-event-sourcing-modelling-devops-process" style="position:relative;"><a href="#production-grade-event-sourcing-modelling-devops-process" aria-label="production grade event sourcing modelling devops process permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Production-Grade Event Sourcing: Modelling, DevOps, Process</h2> <p><strong>Dates:</strong> <a href="https://2024.dddeurope.com/program/production-grade-event-sourcing-modelling-devops-process?utm_source=event_driven_io">27th-29th of May</a></p> <p><strong>The goal of this workshop is to strengthen all of the critical aspects of running event-sourced on production:</strong></p> <ul> <li>modelling (like keeping streams short, managing consistency and efficiently handling business logic, best practices and anti-patterns),</li> <li>managing schema evolution, so events versioning,</li> <li>advanced projections design and resiliency, so how to get the best from events to efficiently get read models and insights from them,</li> <li>DevOps techniques (like traceability, blue-green projection rebuilt, archiving old data),</li> <li>handling distributed processes and integrating with other systems,</li> <li>putting event sourcing as a vital part of the whole architecture (so how to integrate it with non-event-sourced modules, defining private and public schema).</li> </ul> <p><strong>After the workshop, you’ll understand and practice techniques that will allow you to run your Event-Sourcing system on production.</strong></p> <p>All the exercises will be backed by the versatile experience I got throughout my career. I’ll share my experience building systems like Marten and EventStoreDB and working as an architect and consultant in many Event Sourcing projects.</p> <p>We’ll start with the project of the system that looks fine as the first production deployment. Through group exercises, a mixture of modelling and practical coding exercises, we’ll analyse potential issues step by step. We’ll learn how to refactor it step by step without breaking it. That will allow us to fix the boundaries, consistency guarantees, and resiliency. We’ll practice both adding new capabilities and changing existing ones. We’ll practice schema evolution and DevOps practices like projection rebuilds. We’ll end with a more robust and prepared-for-the-next-challenges system.</p> <p><strong>Prerequisities?</strong></p> <p><strong>The workshop is designed for people who already have experience with Event Sourcing.</strong> Especially for those who would like to assess the production-readiness of their design or already deployed system. I won’t start from basics but assume:</p> <ul> <li>experience in how Event Sourcing works (storing events, building state from them),</li> <li>knowledge of how to model business logic and projections/read models,</li> <li>Understand that event sourcing is not event streaming and why streaming solutions like Kafka and Pulsar should not be used as event stores.</li> </ul> <p><strong>I assume you know modelling techniques like EventStorming, C4 and modularity concepts (microservices, monolith, CQRS, etc.).</strong> And that you were already building and designing web applications.</p> <p>Most of the exercises will be focused on general understanding and will not be technology-specific, but practical will require knowledge of one of the languages: C#, Java, or TypeScript. We’ll use Marten and EventStoreDB as event stores examples.</p> <p><strong>If you took my Practical Introduction workshop, you’ll also be fine with taking the Advanced workshop.</strong> Especially that, you’ll also have three weeks to do some additional preparations. I can help you spend this time as well as you can!</p> <p>Interested? <a href="https://2024.dddeurope.com/program/production-grade-event-sourcing-modelling-devops-process?utm_source=event_driven_io">Sign up here</a>.</p> <h2 id="cant-be-there-or-want-a-private-experience" style="position:relative;"><a href="#cant-be-there-or-want-a-private-experience" aria-label="cant be there or want a private experience permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Can’t be there or want a private experience?</h2> <p>No worries, I also run private trainings and other forms of help like:</p> <ul> <li>architecture review,</li> <li>consultancy,</li> <li>proof of concepts,</li> <li>mentoring.</li> </ul> <p>I’m happy to help!</p> <p><strong>If you don’t know what to choose, don’t hesitate <a href="mailto:[email protected]">to contact me via email</a></strong>. I’m here to help.</p> <p>Check the details on my <a href="/en/training/">training</a> page.</p> <p><strong>A workshop is the most effective way to jump-start.</strong></p> <p>You can also try to do <a href="/pl/introduction_to_event_sourcing/">Introduction to Event Sourcing - Self-Paced Kit</a> on your own. I think that’s a decent path, but longer, the choice is yours!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to tackle compatibility issues in ECMA Script modules (and in general)]]>https://event-driven.io/en/how_to_tackle_esmodules_compatibility_issues/https://event-driven.io/en/how_to_tackle_esmodules_compatibility_issues/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4b1031627c090e7dba0a8ec4cb3076e5/a331c/2024-03-09-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAC4jAAAuIwF4pT92AAADnklEQVQozwGTA2z8ALKMYbWPYLuYeL+opr2wvsCprsWik8mji86ojNCpjdKqjdKqkdWultm1nNq1pOLDueTErN20h9isedilcQCyiF22jGC3jmi7ln++nJO/nZDDm4XHnoPPpofTqozOqY/Kp5W9n5K/ppTBoYqzlnzCoXnftILvvYntvIYAu5BovJFou5Rtv5l2xZ+EyqOLvaGQrpOFqpKGiX16ioF/hXJjlHVOsYpdxJlt16d07LmF+8mT/cuW/tGaAMWYbsaZb7+YcriUcbuVbY1xXVdVVmNiZmJiamdpdWtseJeEc9qqe+u6kvLDnvbInv/Snf/Xnv/aoP3SnACrimiOcVCedklxWExsWlFZUlo+NzZxb3VoZnB1c3t0cnyHgYWbhniWhnuMe2mpjnPFoXvMqIPXtY7yx5QAZlhQW0o6h25TVlhXWlVGW2JkPENVbWpvc3J7bGxya2hwZl9ccWxomYBqjmtPdFxIcVlIPTIvfXl60b2fAF9ILkk1IFdFNW5OOXVLM19maDpASjs2M0I8OVpVT15SOz0zIpiGcNqtd9mrd7WIWmFKNEQuGEo9M97ClwCifE+abDiIXzBPRUNJREFOPiVSNhZYPBtLLQtZUUCijmqHdFbKoG7cqXHjsXn3wH+0hFN5XULGnWr3zIwAvJVns41im3pUeF46l3I/soNGrX9Fr4FLrYNMeF4yeGlSo4tt4LB13q566rV/6LuQyauRgXlyvqOD3LeDAKuIZaGVkpuOi2VXRJ6CX76XabSWcW5ZRHReQ0s8Kkc7Ln1uXsygb9endKB/XoiDg7Wjh49wUKiKYNKsegCXeV2dkY2KfnV1YEG7nnurkHTGqIc+Oz0WHCYgISpmVUFQST6NeF2OfmqEe2tzZk/Gro6rknKKdljatYAAk3hfnJGOg3VsbVg8y6yHrZF0wKF/Pjw7GB4oJCUsgm1YYU08dWZRaFhCaltFY1lJqZZ3gmxMnoZk/9CKAJZ/bKeanYV1Z3tkSI50V5FxUJt+YS0pJikeFg4MEFdHOXNTNkIzI4NtTKaATKmBTMGSV4dsSdawePzHgACDZ096ZVVSQDBcRSqGXjCVajeEYTWIZTmWbDl2ViyEXC+PYjCRZjKBYDeLcE+fglyTeli+m2jsuXPquXYAk2tCg1wsfFcqjWIwsYBFtYVJsoJHsoFIr35IsoFItYNIsH9Er31CsHxAp3lAsoxW06Rj57Np47Fz57d8NvbAaLbaC/kAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4b1031627c090e7dba0a8ec4cb3076e5/a331c/2024-03-09-cover.png" srcset="/static/4b1031627c090e7dba0a8ec4cb3076e5/36ca5/2024-03-09-cover.png 200w, /static/4b1031627c090e7dba0a8ec4cb3076e5/a3397/2024-03-09-cover.png 400w, /static/4b1031627c090e7dba0a8ec4cb3076e5/a331c/2024-03-09-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Do you recall moments when you’re sitting and closing dozens or more browser tabs?</strong> Most of them are Google, GitHub, Blogs, and others. You click and close them one by one, not even checking if they’re worth keeping. That’s a sign you learned more about the problem than you ever wanted. But that also means that you solved it. If you didn’t, you’d just add more open tabs. I have that feeling fresh now.</p> <p>Yesterday, I solved one of those types of issues. It’s not so surprising knowing that I <a href="/en/introducing_emmett/">just started Emmett</a>, a JS/TS library for event-driven applications. JavaScript Land has a lot of rabbit holes. And boy, you can go down in them!</p> <p>JavaScript can be seen as wild, but at least they have the ECMAScript specification that unifies standards. It’s widely discussed before acceptance but also inert in application.</p> <p><strong>For instance, ES Modules (ECMAScript Modules) were introduced in ECMAScript 6 (ES6) in <a href="https://262.ecma-international.org/6.0/">2015</a>.</strong> Yes, the year when Netflix released Narcos, Jon Snow died, and Bruno Mars’ Uptown Funk was on top of Billboard charts. Nice memories? Guess what? For the JS community, they’re still fresh, as migration to ES Modules is still ongoing.</p> <h2 id="es-modules" style="position:relative;"><a href="#es-modules" aria-label="es modules permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>ES Modules</h2> <p>What are ES Modules? They brought a standardised module system to JavaScript, addressing several limitations of the previously widely used module patterns (such as CommonJS and AMD). ES Modules were introduced for several reasons:</p> <ol> <li><strong>Standardisation:</strong> Before ES Modules, the JavaScript ecosystem was fragmented with multiple module systems (e.g., CommonJS for Node.js and AMD for asynchronous module loading in browsers). ES Modules provide a single, standardised module system used across different environments, enhancing interoperability.</li> <li><strong>Static Analysis and Tree Shaking</strong> ES Modules support static analysis, allowing tools to perform optimisations such as tree shaking (eliminating unused code) due to their static structure (import and export statements are at the top level and cannot be conditional).</li> <li><strong>Improved Performance:</strong> Due to the possibility of static analysis, JavaScript engines can optimise the loading and bundling of modules more efficiently, potentially improving load times and execution performance.</li> <li><strong>Better Code Organization.</strong> ES Modules encourage a more modular and maintainable code structure, making it easier to develop, understand, and maintain large codebases.</li> <li><strong>Native Browser Support.</strong> ES Modules are natively supported in modern browsers, allowing modules to be used directly without needing module bundlers or transpilers.</li> </ol> <p>All that sounds great, but migration can be tedious. You need to change not only your build tooling but also the syntax in the code. For instance, in CommonJS, your module declaration could look like:</p> <div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">+</span> b<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">subtract</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">-</span> b<span class="token punctuation">;</span> module<span class="token punctuation">.</span>exports <span class="token operator">=</span> <span class="token punctuation">{</span> add<span class="token punctuation">,</span> subtract <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>And usage with imports as:</p> <div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">const</span> math <span class="token operator">=</span> <span class="token function">require</span><span class="token punctuation">(</span><span class="token string">'./math'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>math<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>math<span class="token punctuation">.</span><span class="token function">subtract</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>In ES modules, the declaration looks:</p> <div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">add</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">+</span> b<span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">subtract</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token parameter">a<span class="token punctuation">,</span> b</span><span class="token punctuation">)</span> <span class="token operator">=></span> a <span class="token operator">-</span> b<span class="token punctuation">;</span></code></pre></div> <p>And usage:</p> <div class="gatsby-highlight" data-language="javascript"><pre class="language-javascript"><code class="language-javascript"><span class="token keyword">import</span> <span class="token punctuation">{</span> add<span class="token punctuation">,</span> subtract <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./math.js'</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token function">subtract</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>With <a href="https://requirejs.org/docs/whyamd.html#amd">AMD</a>, the leap is even bigger. So yeah, essentially, you need to go through all files and gradually migrate. For known reasons, that’s hard and takes time. Not always that’s justified financial-wise. The library creators have to support both, which, for obvious reasons, is tricky.</p> <p>Do you know <a href="https://en.wikipedia.org/wiki/Quine_(computing)">Quines</a>? Per Wikipedia:</p> <blockquote> <p>A quine is a computer program that takes no input and produces a copy of its own source code as its only output.</p> </blockquote> <p>The variations of quines are Ouroboros programs:</p> <blockquote> <p>The quine concept can be extended to multiple levels of recursion, giving rise to “ouroboros programs”, or quine-relays. This should not be confused with multiquines.</p> </blockquote> <p>There are people, like Yusuke Endoh, that do things like <a href="https://github.com/mame/quine-relay">Quine Relay</a>:</p> <blockquote> <p>QR.rb is a Ruby program that generates a Rust program that generates a Scala program that generates …(through 128 languages in total)… a REXX program that generates the original Ruby code again.</p> </blockquote> <p>Yeah, people are having fun.</p> <p>In JS land, you might not have fun, but you may need to write your quines. How do you write code that supports both styles, CommonJS and ES Modules? There are multiple options. You can use:</p> <ul> <li>separate file extensions <em>.mjs</em> for ES Modules and <em>.js</em> for CommonJS,</li> <li>TypeScript and ask the TS compiler to generate files compatible with both. That’s nice, as TypeScript is ECMA Script compatible by itself.</li> <li>bundlers that shake and bake and deliver nice code to you.</li> </ul> <p>Sweet, but all has its price. Having multiple files for the same code might lead to the dual package hazard, where both versions of your package could be loaded simultaneously, potentially leading to bugs and bloated bundles. If you’re a package creator, then that means your life gets wilder from time to time. Package Creator? Yeah, that’s me.</p> <h2 id="issues-with-es-modules" style="position:relative;"><a href="#issues-with-es-modules" aria-label="issues with es modules permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Issues with ES Modules</h2> <p>Getting back to my closing browser tabs habit.</p> <p>I recently released <a href="https://github.com/event-driven-io/emmett/releases/">several of Emmett’s versions</a>. They brought:</p> <ul> <li><a href="https://developers.eventstore.com/">EventStoreDB</a> support,</li> <li>Integration and Acceptance testing for WebAPI,</li> <li>basic <a href="https://fastify.dev/">Fastify</a> support,</li> <li>state time travelling capabilities,</li> <li>and more.</li> </ul> <p>I was happy and kinda proud as external contributors delivered some of those changes. That’s motivating, especially in the early phases of the OSS project. But happiness has its deadline. For OSS, this deadline comes when someone tries to use a brand-new version.</p> <p><a href="https://github.com/event-driven-io/emmett/issues/38">Emmett appears incompatible with ES Modules</a>. That was surprising, as I was using them in the code, and I thought I had configured that correctly. Famous last words!</p> <p>While importing, you got:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">import</span> <span class="token punctuation">{</span> getInMemoryEventStore <span class="token punctuation">}</span> from <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> ^^^^^^^^^^^^^^^^^^^^^ SyntaxError: Named <span class="token builtin class-name">export</span> <span class="token string">'getInMemoryEventStore'</span> not found. The requested module <span class="token string">'@event-driven-io/emmett'</span> is a CommonJS module, <span class="token function">which</span> may not support all module.exports as named exports. CommonJS modules can always be imported via the default export, <span class="token keyword">for</span> example using: <span class="token function">import</span> pkg from <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> const <span class="token punctuation">{</span> getInMemoryEventStore <span class="token punctuation">}</span> <span class="token operator">=</span> pkg<span class="token punctuation">;</span> at ModuleJob._instantiate <span class="token punctuation">(</span>node:internal/modules/esm/module_job:132:21<span class="token punctuation">)</span> at async ModuleJob.run <span class="token punctuation">(</span>node:internal/modules/esm/module_job:214:5<span class="token punctuation">)</span> at async ModuleLoader.import <span class="token punctuation">(</span>node:internal/modules/esm/loader:329:24<span class="token punctuation">)</span> at async loadESM <span class="token punctuation">(</span>node:internal/process/esm_loader:28:7<span class="token punctuation">)</span> at async handleMainPromise <span class="token punctuation">(</span>node:internal/modules/run_main:113:12<span class="token punctuation">)</span></code></pre></div> <p>Of course, I couldn’t reproduce that locally, but <em>“works on my box!”</em> isn’t the answer your users would expect. So, I had to dig deeper. I’m using TypeScript. I checked the generated files, and they seemed fine. They had all those <em>.js</em>, <em>.mjs</em>, <em>.ts</em>, <em>.mts</em> files, etc. Yet, I found the potential issue in those closed tabs. I didn’t give a hint about which files are entry points for CommonJS imports and which for ES Modules.</p> <p>Essentially, you need to declare it in your <em>package.json</em> file like this:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token string">"@event-driven-io/emmett"</span><span class="token punctuation">,</span> <span class="token property">"description"</span><span class="token operator">:</span> <span class="token string">"Emmett - Event Sourcing development made simple"</span><span class="token punctuation">,</span> <span class="token comment">// (...)</span> <span class="token property">"exports"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"."</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"import"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"types"</span><span class="token operator">:</span> <span class="token string">"./dist/index.d.mts"</span><span class="token punctuation">,</span> <span class="token property">"default"</span><span class="token operator">:</span> <span class="token string">"./dist/index.mjs"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"require"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"types"</span><span class="token operator">:</span> <span class="token string">"./dist/index.d.ts"</span><span class="token punctuation">,</span> <span class="token property">"default"</span><span class="token operator">:</span> <span class="token string">"./dist/index.js"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"main"</span><span class="token operator">:</span> <span class="token string">"./dist/index.js"</span><span class="token punctuation">,</span> <span class="token property">"module"</span><span class="token operator">:</span> <span class="token string">"./dist/index.mjs"</span><span class="token punctuation">,</span> <span class="token property">"types"</span><span class="token operator">:</span> <span class="token string">"./dist/index.d.ts"</span><span class="token punctuation">,</span> <span class="token property">"files"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token string">"dist"</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span></code></pre></div> <p>Now, when someone uses <em>require</em> syntax from CommonJS, it goes to the <em>js</em> and <em>ts</em> files; if someone uses ES Modules, it goes to <em>ts</em> and <em>mts</em>. That’s cool, but I wasn’t still able to reproduce it.</p> <p>Providing a quick fix is fine, but if you don’t know how to verify it, it is not a fix; it’s a rumour. That can lead to regression when you introduce a new change that breaks it again.</p> <p>I wouldn’t like to have that. A good indicator of the OSS community quality is the engagement from users. And it sounds like Emmett already has a thriving community. <a href="https://github.com/event-driven-io/emmett/issues/40">I got reproducible steps with a project</a>; Thanks <a href="https://github.com/stepaniukm">Mateusz</a>! But yeah, one problem solved, and a new one appears. Mateusz confirmed my suspicion about the missing exports in <em>package.json</em> was correct; that solved one issue, but another one popped up for him. Of we go to detective work, thanks again, Mateusz!</p> <p>The next problem was:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">Directory <span class="token function">import</span> <span class="token string">'emmett/packages/emmett/dist/commandHandling'</span> is not supported resolving ES modules imported from emmett/packages/emmett/dist/index.mjs</code></pre></div> <p>I’m exporting them to the root <em>’.’</em> to provide a straightforward entry point for Emmett’s features without searching through nested structures. You can import all code like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token keyword">type</span> <span class="token punctuation">{</span> Event<span class="token punctuation">,</span> Command <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> IllegalStateError<span class="token punctuation">,</span> CommandHandler <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@event-driven-io/emmett'</span><span class="token punctuation">;</span></code></pre></div> <p>Yet, of course, I don’t keep all the source codes in a single file; <a href="https://github.com/event-driven-io/emmett/tree/main/packages/emmett/src">I have a nested structure</a>. I’m using a feature called <em>“re-exporting”</em> to achieve that. Essentially, I have a default <em>index.ts</em> file in a directory that imports files from subdirectories and exports them again under the flattened structure. This can look like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">'./commandHandling'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">'./errors'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">'./eventStore'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">'./serialization'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">'./testing'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">'./typing'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">'./utils'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token operator">*</span> <span class="token keyword">from</span> <span class="token string">'./validation'</span><span class="token punctuation">;</span></code></pre></div> <p>And here’s the deal. When working with TypeScript (<em>.ts</em>, <em>.tsx</em>) and ECMAScript Modules (<em>.mjs</em>) in Node.js, you might encounter challenges due to the differences in file extensions and module resolution between TypeScript and JavaScript, particularly when using <em>export *</em> from syntax.</p> <p>TypeScript introduced <em>.mts</em> as the file extension for ECMAScript modules containing TypeScript code, aligning with <em>.mjs</em> for JavaScript ECMAScript modules. Yet, you may face the following issues:</p> <ol> <li><strong>Module Resolution:</strong> Node.js treats <em>.mjs</em> files as ES modules natively. TypeScript’s <em>.mts</em> extension for ES module files also aligns with this convention. However, when importing or re-exporting from these modules, you might encounter issues where TypeScript or Node.js can only correctly resolve the other module type with explicit hints.</li> <li><strong>Type Definitions:</strong> When using export * from in TypeScript to re-export ES module contents from a <em>.mjs</em> file, TypeScript may not be able to apply the correct types unless those types are explicitly declared or inferred from .d.ts (declaration files). This can lead to type-checking issues.</li> <li><strong>Tooling and Build Processes:</strong> Tooling might need additional configuration to handle <em>.mts</em> and <em>.mjs</em> files correctly, especially when both TypeScript and JavaScript modules are mixed in a project. This includes setting up TypeScript, bundlers (Webpack, Rollup), and other tools (Babel) to recognise and process these files appropriately.</li> <li><strong>Path Importing and File Extensions:</strong> TypeScript and Node.js have different default behaviours regarding file extensions. TypeScript might require explicit extensions (e.g., <em>.mts</em>) in import statements, which is not always true for <em>.mjs</em> in Node.js due to its native module resolution strategy.</li> </ol> <p>Plenty of options to make it wrong, aye? Before we discuss the (embarrassing) fix, let’s learn how I reproduced it.</p> <h2 id="checking-the-compatibility" style="position:relative;"><a href="#checking-the-compatibility" aria-label="checking the compatibility permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Checking the compatibility</h2> <p>We’re in the moment when I had a project on which I was able to reproduce the issue. I had some clues about what may be wrong (dozens of clues!). I decided to automate it before providing the fix. Such automation is more complex. As you know now, it’s not possible to ensure that the generated code by the TypeScript compiler is compatible with different module styles. The only way is to test it on the real project.</p> <p>The test project I got was using <a href="https://github.com/nuxt/nuxt">Nuxt.js</a>. Nuxt.js is a framework built on Vue.js that offers server-side rendering, static site generation, and automatic code splitting to simplify Vue app development. It’s chosen for its ease of use, SEO benefits, performance optimisation features, and ability to streamline project setup and deployment.</p> <p>It’s actually not important that it uses this particular framework, but what it brings. As always, strong powers can also be weaknesses. The fact that it has built-in bundling and code splitting and allows using both the backend and front end in one place can cause more compatibility issues. Why?</p> <p>When TypeScript code is bundled, tools like Webpack, Rollup, or Parcel take care of module resolution and dependencies and can transpile the code to ensure compatibility across different environments. These bundlers often smooth over discrepancies between module formats (e.g., CommonJS vs. ES Modules) and file extensions. However, when you work with unbundled TypeScript code in a project that uses ES Modules, several issues can arise, particularly related to module resolution, file extensions, and differences in syntax between TypeScript and native ES Modules in Node.js.</p> <p>Technically, the flow looks as follows</p> <ol> <li>Npm package TypeScript code is transpilled into JavaScript, then minified, bundled and packaged.</li> <li>Now, if you’re using such a package, you need to load the proper JS code, find TypeScript type maps and import it into your application code. Then, if you’re using TypeScript and frameworks like Nuxt, it’ll be transpilled, minified, bundled, and packaged again.</li> </ol> <p>It’s like <a href="https://en.wikipedia.org/wiki/Chinese_whispers">Chinese Whispers game</a>. As in this game, the solution may not be what we expect.</p> <p>The other challenge is that Nuxt wasn’t failing during the build but when it was generating the static HTML files. That’s the power of it: you can run the same code in the backend and browser, but it also adds more trouble. Especially that when running its built-in command:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> run generate --fail-on-error</code></pre></div> <p>It wasn’t failing. It silently succeeded in redirecting errors to <a href="https://bash.cyberciti.biz/guide/Standard_error">Standard Error Output (stderr)</a>. No one said that’s going to be easy, but that’s why we’re here!</p> <p><strong>I decided to take the following path for checking the compatibility:</strong></p> <ol> <li><strong>Put the sample application with import error for ES Modules <a href="https://github.com/event-driven-io/emmett/tree/1624935161f5db6f5f14461d007154da3dd75c00/e2e/esmCompatibility">into Emmett repository</a></strong>. This is important for the <a href="https://martinfowler.com/bliki/TestDrivenDevelopment.html">Red, Green Refactor</a>. Knowing that it fails, we get certain that it’s fixed after the change.</li> <li><strong>Extend the existing GitHub Action pipeline</strong> that builds and tests all packages during each Pull Request and main branch commit.</li> <li><strong>Extend it by building Emmett’s npm package with <em>npm pack</em>.</strong> This creates a <a href="https://en.wikipedia.org/wiki/Tar_(computing)">tarball</a> of your package, simulating what would be uploaded to npm without actually publishing it. This step is crucial for testing the package as it would appear to consumers. I wouldn’t like to publish the package only to realise that I broke compatibility. I needed to do it before that.</li> <li><strong>Install such tarball file as a package reference in the sample application.</strong> Npm allows doing that without additional steps to set up the package server or file share. The soundest way to validate integration and compatibility is to install the package in a sample project that uses ES Modules and attempts to import code from your package.</li> <li><strong>Build the Sample Project and check if there are no errors now.</strong></li> </ol> <p>That sounds simple, but how do you tackle it? Let’s go through it step by step.</p> <p>The first two steps are pretty straightforward. I copied the project as it is to ensure that I’m following the reproduction steps. I also had the initial pipeline. Let’s go to the next steps</p> <h3 id="extend-it-by-building-emmetts-npm-package-with-npm-pack" style="position:relative;"><a href="#extend-it-by-building-emmetts-npm-package-with-npm-pack" aria-label="extend it by building emmetts npm package with npm pack permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Extend it by building Emmett’s npm package with <em>npm pack</em>.</h3> <p>In Emmett, I’m using a mono repo based on the <a href="https://docs.npmjs.com/cli/v10/using-npm/workspaces">NPM Workspaces</a> to tame the complexity of building multiple packages. That’s a topic on its own, so let’s leave the details for another blog article. Most importantly, all packages are nested in the <em>packages</em> folder, and npm has built-in ways to work only on specific packages. To pack a particular package (in this case, core Emmett package), you need to call:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> pack -w @event-driven-io/emmett</code></pre></div> <p>The <em>-w</em> parameter means we’re running the command for specific workspaces. In this case, the core package workspace. The command will generate a tarball in the root folder. That’s not perfect for our case, especially since it’ll have the package version in its name, for instance: <em>event-driven-io-emmett-0.5.2.tgz</em>. That means it’s not repeatable; when we change the version, it’ll have a different one.</p> <p><strong>Luckily <em>npm pack</em> have more options. We can specify the file destination and also return the result metadata in JSON format:</strong></p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> pack --json --pack-destination <span class="token string">'./e2e/esmCompatibility'</span> -w @event-driven-io/emmett</code></pre></div> <p>The <em>./e2e/esmCompatibility</em> is the location of my sample project.</p> <p>Result JSON will contain all metadata about the generated package. We don’t need all of them; we just want to extract the file name. We’re running commands in GitHub actions, so Linux, why don’t we use some of the native goodies like pipelining and tools like <a href="https://jqlang.github.io/jq/manual/">jq</a>? <em>jq</em> tool is a lightweight and flexible command-line JSON processor, enabling users to easily parse, filter, and transform JSON data. That’s precisely what we need!</p> <p>We can do:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> pack --json --pack-destination <span class="token string">'./e2e/esmCompatibility'</span> -w @event-driven-io/emmett <span class="token operator">|</span> <span class="token punctuation">\</span> jq -r <span class="token string">'.[] | .filename'</span></code></pre></div> <p>This will return the raw value of the <em>filename</em> property of the object in the results array. Why do we need it? In the next step, we’ll need to pass it to the <em>npm install</em> command.</p> <p><strong>We must export the filename to some GitHub Actions environment variable to make it available in a separate step of the GitHub Action pipeline.</strong> GitHub action allows us to do that by:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Pack Emmett locally to tar file <span class="token key atrule">shell</span><span class="token punctuation">:</span> bash <span class="token key atrule">run</span><span class="token punctuation">:</span> echo "PACKAGE_FILENAME=$(our bash script)" <span class="token punctuation">></span><span class="token punctuation">></span> $GITHUB_ENV</code></pre></div> <p>So our whole step will look like:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Pack Emmett locally to tar file <span class="token key atrule">shell</span><span class="token punctuation">:</span> bash <span class="token key atrule">run</span><span class="token punctuation">:</span> echo "PACKAGE_FILENAME=$(npm pack <span class="token punctuation">-</span><span class="token punctuation">-</span>json <span class="token punctuation">-</span><span class="token punctuation">-</span>pack<span class="token punctuation">-</span>destination './e2e/esmCompatibility' <span class="token punctuation">-</span>w @event<span class="token punctuation">-</span>driven<span class="token punctuation">-</span>io/emmett <span class="token punctuation">|</span> jq <span class="token punctuation">-</span>r '.<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">|</span> .filename')" <span class="token punctuation">></span><span class="token punctuation">></span> $GITHUB_ENV</code></pre></div> <h3 id="install-such-tarball-file-as-a-package-reference-in-the-sample-application" style="position:relative;"><a href="#install-such-tarball-file-as-a-package-reference-in-the-sample-application" aria-label="install such tarball file as a package reference in the sample application permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Install such tarball file as a package reference in the sample application</h3> <p>This part is simple; our sample application already has the tarball file with packed library code. We have the filename in our environment variable. Let’s use it:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Use it in the compatibility test project <span class="token key atrule">working-directory</span><span class="token punctuation">:</span> ./e2e/esmCompatibility <span class="token key atrule">run</span><span class="token punctuation">:</span> npm install $<span class="token punctuation">{</span><span class="token punctuation">{</span> env.PACKAGE_FILENAME <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre></div> <p>Just to be sure that packages were refreshed, we can also run <em>npm install</em>:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install packages in the compatibility test project <span class="token key atrule">working-directory</span><span class="token punctuation">:</span> ./e2e/esmCompatibility <span class="token key atrule">run</span><span class="token punctuation">:</span> npm install</code></pre></div> <h3 id="build-the-sample-project-and-check-if-there-are-no-errors-now" style="position:relative;"><a href="#build-the-sample-project-and-check-if-there-are-no-errors-now" aria-label="build the sample project and check if there are no errors now permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Build the Sample Project and check if there are no errors now</h3> <p>To reproduce the issue, we need to try to build a sample project. As mentioned earlier, the challenge with this Nuxt setup is that Nuxt does not fail during the build but when it generates the static HTML files. It silently succeeded in redirecting errors to <a href="https://bash.cyberciti.biz/guide/Standard_error">Standard Error Output (stderr)</a>. The error lines will look as that:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">Error: <span class="token punctuation">[</span>nuxt<span class="token punctuation">]</span> <span class="token punctuation">[</span>request error<span class="token punctuation">]</span> <span class="token punctuation">[</span>unhandled<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token number">500</span><span class="token punctuation">]</span> Directory <span class="token function">import</span> <span class="token string">'/home/runner/work/emmett/emmett/e2e/esmCompatibility/node_modules/@event-driven-io/emmett/dist/commandHandling'</span> is not supported resolving ES modules imported from /home/runner/work/emmett/emmett/e2e/esmCompatibility/node_modules/@event-driven-io/emmett/dist/index.mjs Error: <span class="token punctuation">[</span>nuxt<span class="token punctuation">]</span> <span class="token punctuation">[</span>request error<span class="token punctuation">]</span> <span class="token punctuation">[</span>unhandled<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token number">500</span><span class="token punctuation">]</span> Directory <span class="token function">import</span> <span class="token string">'/home/runner/work/emmett/emmett/e2e/esmCompatibility/node_modules/@event-driven-io/emmett/dist/commandHandling'</span> is not supported resolving ES modules imported from /home/runner/work/emmett/emmett/e2e/esmCompatibility/node_modules/@event-driven-io/emmett/dist/index.mjs</code></pre></div> <p>So we’ll need to do more lifting and parse the output. Again, Linux tools like <a href="https://man7.org/linux/man-pages/man1/grep.1.html">grep</a> are pretty straightforward. As long as you know the syntax, which wasn’t my precise case, I had to learn. The grep command searches through input text, filtering for lines that match a specified pattern, and outputs those lines.</p> <p>The challenge with grep is that we need to fail our script when matches are found, and grep doesn’t do that. It returns found lines. Yet, we can deal with that with a simple if. Our shell script can look as follows:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token keyword">if</span> <span class="token function">npm</span> run generate <span class="token operator"><span class="token file-descriptor important">2</span>></span><span class="token file-descriptor important">&amp;1</span> <span class="token operator">|</span> <span class="token function">grep</span> -iF <span class="token string">'[request error]'</span><span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token builtin class-name">echo</span> <span class="token string">"Errors found, failing the step."</span> <span class="token operator">&amp;&amp;</span> <span class="token builtin class-name">exit</span> <span class="token number">1</span> <span class="token keyword">else</span> <span class="token builtin class-name">echo</span> <span class="token string">"No errors found, proceeding..."</span> <span class="token keyword">fi</span></code></pre></div> <p>The script runs npm run generate, and <em>2>&#x26;1</em> error messages with the standard output to be scanned together. 2 stands for <em>stderr</em> and 1 for <em>stdout</em>. This merging is crucial because it ensures no message is missed, whether it’s an error or not.</p> <p>After merging, the script uses the pipeline operator <em>|</em> to send this combined stream to <em>grep</em>, which looks for <em>“[request error]</em>. The <em>—F</em> means it treats the search phrase as a fixed string, not a regexp pattern. The <em>-i</em> param makes the search case-insensitive.</p> <p>Finding this phrase triggers <em>“Errors found, failing the step.”</em> and stops the script with exit 1, indicating an error occurred. It is crucial to tell GitHub Actions that the step failed.</p> <p>If the phrase isn’t found, “No errors found, proceeding…” is printed, and the step succeeds.</p> <h3 id="complete-pipeline" style="position:relative;"><a href="#complete-pipeline" aria-label="complete pipeline permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Complete pipeline</h3> <p>The complete pipeline with all the steps <a href="https://github.com/event-driven-io/emmett/blob/231174185a23eb3d0a26ba52270ba7c4cc2312bb/.github/workflows/build_and_test.yml">looks as follows</a>:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Build and test <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token comment"># run it on push to the default repository branch</span> <span class="token key atrule">push</span><span class="token punctuation">:</span> <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>main<span class="token punctuation">]</span> <span class="token comment"># run it during pull request</span> <span class="token key atrule">pull_request</span><span class="token punctuation">:</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">build-and-test-code</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build application code <span class="token comment"># use system defined below in the tests matrix</span> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> matrix.os <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">strategy</span><span class="token punctuation">:</span> <span class="token comment"># define the test matrix</span> <span class="token key atrule">matrix</span><span class="token punctuation">:</span> <span class="token comment"># selected operation systems to run CI</span> <span class="token key atrule">os</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>ubuntu<span class="token punctuation">-</span>latest<span class="token punctuation">]</span> <span class="token comment">#, windows-latest, macos-latest]</span> <span class="token comment"># selected node version to run CI</span> <span class="token key atrule">node-version</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>20.11.1<span class="token punctuation">]</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Check Out Repo <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v4 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Use Node.js $<span class="token punctuation">{</span><span class="token punctuation">{</span> matrix.node<span class="token punctuation">-</span>version <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>node@v4 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token comment"># use the node version defined in matrix above</span> <span class="token key atrule">node-version</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> matrix.node<span class="token punctuation">-</span>version <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">cache</span><span class="token punctuation">:</span> <span class="token string">'npm'</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install dependencies <span class="token key atrule">run</span><span class="token punctuation">:</span> npm ci <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build TS <span class="token key atrule">run</span><span class="token punctuation">:</span> npm run build<span class="token punctuation">:</span>ts <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Run linting (ESlint and Prettier) <span class="token key atrule">run</span><span class="token punctuation">:</span> npm run lint <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build <span class="token key atrule">run</span><span class="token punctuation">:</span> npm run build <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Test <span class="token key atrule">run</span><span class="token punctuation">:</span> npm run test <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Pack Emmett locally to tar file <span class="token key atrule">shell</span><span class="token punctuation">:</span> bash <span class="token key atrule">run</span><span class="token punctuation">:</span> echo "PACKAGE_FILENAME=$(npm pack <span class="token punctuation">-</span><span class="token punctuation">-</span>json <span class="token punctuation">-</span><span class="token punctuation">-</span>pack<span class="token punctuation">-</span>destination './e2e/esmCompatibility' <span class="token punctuation">-</span>w @event<span class="token punctuation">-</span>driven<span class="token punctuation">-</span>io/emmett <span class="token punctuation">|</span> jq <span class="token punctuation">-</span>r '.<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">|</span> .filename')" <span class="token punctuation">></span><span class="token punctuation">></span> $GITHUB_ENV <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Use it in the compatibility test project <span class="token key atrule">working-directory</span><span class="token punctuation">:</span> ./e2e/esmCompatibility <span class="token key atrule">run</span><span class="token punctuation">:</span> npm install $<span class="token punctuation">{</span><span class="token punctuation">{</span> env.PACKAGE_FILENAME <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Install packages in the compatibility test project <span class="token key atrule">working-directory</span><span class="token punctuation">:</span> ./e2e/esmCompatibility <span class="token key atrule">run</span><span class="token punctuation">:</span> npm install <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build the compatibility test project final <span class="token key atrule">working-directory</span><span class="token punctuation">:</span> ./e2e/esmCompatibility <span class="token key atrule">shell</span><span class="token punctuation">:</span> bash <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string"> if npm run generate 2>&amp;1 | grep -iF '[request error]'; then echo "Errors found, failing the step." &amp;&amp; exit 1 else echo "No errors found, proceeding..." fi</span></code></pre></div> <h3 id="the-fix" style="position:relative;"><a href="#the-fix" aria-label="the fix permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The Fix</h3> <p>Ok, reproduction steps are great, but what did I do to fix the issue? The Fix and source of failure were somewhat embarrassing (as always).</p> <p><strong>I’m using <a href="https://github.com/egoist/tsup">tsup</a> to bundle and minimise my package code.</strong> <em>tsup</em> is known for its simplicity and efficiency. It focuses on producing minimal and fast JavaScript output. It automatically handles TypeScript compilation and has built-in support for tree shaking, which helps reduce the bundle size. It’s a great and simple tool, but it can still fail in the hands of sloppy developers like me.</p> <p>You configure it through the <em>tsup.config.ts</em>. My looked look like this:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> defineConfig <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'tsup'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> env <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span> splitting<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> clean<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// clean up the dist folder</span> dts<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// generate dts files</span> format<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'cjs'</span><span class="token punctuation">,</span> <span class="token string">'esm'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// generate cjs and esm files</span> minify<span class="token operator">:</span> env <span class="token operator">===</span> <span class="token string">'production'</span><span class="token punctuation">,</span> bundle<span class="token operator">:</span> env <span class="token operator">===</span> <span class="token string">'production'</span><span class="token punctuation">,</span> skipNodeModulesBundle<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> entryPoints<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'src/index.ts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> watch<span class="token operator">:</span> env <span class="token operator">===</span> <span class="token string">'development'</span><span class="token punctuation">,</span> target<span class="token operator">:</span> <span class="token string">'esnext'</span><span class="token punctuation">,</span> outDir<span class="token operator">:</span> <span class="token string">'dist'</span><span class="token punctuation">,</span> entry<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">'src/**/*.ts'</span><span class="token punctuation">,</span> <span class="token string">'!src/**/*.spec.ts'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">//include all files under src but not specs</span> sourcemap<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> tsconfig<span class="token operator">:</span> <span class="token string">'tsconfig.build.json'</span><span class="token punctuation">,</span> <span class="token comment">// workaround for https://github.com/egoist/tsup/issues/571#issuecomment-1760052931</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Guess what I did wrong?</p> <p>Yup, I tried to be sneaky. And writing sneaky code is not the smartest move. I didn’t set the env before publishing to <em>production</em>. Because of that, it wasn’t bundling and minimising code, but leaving it nested.</p> <p>When code is bundled, it’s effectively placed in a single file (well, almost; it’s chunked to allow asynchronous, gradual loading). Thanks to that, you don’t get the issue of referencing the wrong files of the different module types. Depending on the tooling configuration, unbundled code may have issues locating the proper ES Modules or CommonJS files. With bundling, this problem disappears. So The Fix was just to change those two lines from:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> defineConfig <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'tsup'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> env <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token comment">// (...)</span> minify<span class="token operator">:</span> env <span class="token operator">===</span> <span class="token string">'production'</span><span class="token punctuation">,</span> bundle<span class="token operator">:</span> env <span class="token operator">===</span> <span class="token string">'production'</span><span class="token punctuation">,</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>to</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> defineConfig <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'tsup'</span><span class="token punctuation">;</span> <span class="token keyword">const</span> env <span class="token operator">=</span> process<span class="token punctuation">.</span>env<span class="token punctuation">.</span><span class="token constant">NODE_ENV</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token function">defineConfig</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token comment">// (...)</span> minify<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> bundle<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Curtain!</p> <h3 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h3> <p>Reflecting on this whirlwind journey of making Emmett compatible with ES Modules and CommonJS has been quite the ride. Delivering OSS packages is a decent opportunity to learn new stuff. But not always; you’d like to have such opportunities so often. Despite being a 2015 initiative, the move to ES Modules feels as fresh and ongoing as ever. It’s like keeping pace with the never-ending updates in our favourite TV series.</p> <p><strong>One may say that JS land is terrible, and what I did is too much of a heavy lifting. And one could be right. But even though I got the hard lesson, I see that as positive.</strong> JS community is thriving. The diversity can sometimes be overwhelming, but at least you have a choice and tools to fix it. Having too big a choice is still better than monoculture and the single way. Especially if you have standards to which the community is moving.</p> <p>Transitions are always hard, and compatibility is never easy. I hope this article explains how to deal with compatibility issues and harder-to-find bugs. It’s essential to find a repeatable way to avoid regression and then make the fix.</p> <p><strong>The approach explained can also give you a general mental framework for tackling compatibility issues in other environments.</strong></p> <p>Last but not least, go check <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a>. It’ll take your event-driven applications back to the future.</p> <p><strong><a href="https://discord.gg/fTpqUTMmVa">Join also our Discord</a> to get a live stream of fun like this!</strong></p> <p>See also the PRs with fixes:</p> <ul> <li><a href="https://github.com/event-driven-io/emmett/pull/39">https://github.com/event-driven-io/emmett/pull/39</a></li> <li><a href="https://github.com/event-driven-io/emmett/pull/41">https://github.com/event-driven-io/emmett/pull/41</a></li> <li><a href="https://github.com/event-driven-io/emmett/pull/42">https://github.com/event-driven-io/emmett/pull/42</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Keeping our overachieving freak on a leash]]>https://event-driven.io/en/keeping_overachieving_freak_on_a_leash/https://event-driven.io/en/keeping_overachieving_freak_on_a_leash/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d83a0d0e9897abf4dbc273a6793f03c7/a331c/2024-03-01-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAC4jAAAuIwF4pT92AAADnklEQVQozwGTA2z8ABAdKhAeKREeKhUjLhQjLRYkLxclMRQiLhclMhUiLxMgKxUiMCAtOy47SBwpOBckNBsmNAMOHg8bKwsUJQANGygLFR0JEhsOGCETISsOHCcNGyYMGSMPGyUOGSMNGCIDDhpZZW5ibngACRhWY210gYtKVmITHSoIDx8AOD87BA4WCxYgDRolHiUrLjEvKi4tGiMoDRghCxYhDhkiAAYScHyESVVfEBonYm52hpOai5ifHCcwBg4dAKGjgj1EPBwjJlBIPnNpU46JbZ2YeHl6aQ4ZHwsVHQsVHwALF3F8hUxWXGVwd5mmraCutDhCTis0QwIJGgDc6LvAvpJ1YER8dV2JiHKAfWaLim+TlIESGyIDDRYXICcpMSxsdHNtd3xBTFR8iJGLl54tNkM1PksACBgA2OO2yruHb2ZOgIBmc3BcmJd8n5d2mI1sg3hdbW5fYmVOS087TlVVZnF7X2t1a3aAXGdxJi88OEFPAQgbAMvJmJ+San16XnZzW3FuWKalhLy3kLalc6ubdZqZfWltUkBGOzA5QE5aYZCepElTXQAJFg4WJh0mNQQMHgCUglt6eF9xb1iAfmSnpoeio4uyn3Gaj22lpH2JiWtudWhRWEwwNzgxO0IhKjQbJC8VHioLEyELEiIKEiMAyJddgWdFe3tjrq6KtbWXh4h9fm9QuLWHtreGrayHnqCMjpOCc3lwS1JRQ0xTN0BJMTtERk9YOkJMDhUmAKeBTaCBV7S3jrW1kXh3ZpmWerOic8vIlMPHmb7Aj7a2hLOyg5+edYKDYFZZRy0zLxghJRkjKRIbJQsTIQB1XjOeiWOqrIp9fGpgYFKNi26FgmaPi2nO06LKz57GypnGypvFx5m5uIikoXKDgmBTVkQnLSsTHSMUHSYAaVcxfWZGbm5eb21ZZWRUYGBSU1JDgX9bs7OCuLiHtbSDt7eGwcSVu72OubeFsq56mZZpbGtNNjszISgqAE5KLYZlOlRQQEJDN0tIO2JhVYSBYG9qP2xoQXh0S356T3VxRoeDV6mndri5ibW2hbCueqSgbnp2T1JSOwBGRSxwVS53a1FFRz5qZVFqaFlxcFdXUzFZVTVaVjVaVjRaVjZZVTR3ckqvrXy5uo2tq3mvqXOdlmJ/ek4APD0qX0oncl9GZ2hZWFNFYV5OX1pCUEwrUUwsUk4vVFAvWFQzXFg3YFs4lZBgr6p3q6VxrKRumpFfhoFTbURHrVLzw2kAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/d83a0d0e9897abf4dbc273a6793f03c7/a331c/2024-03-01-cover.png" srcset="/static/d83a0d0e9897abf4dbc273a6793f03c7/36ca5/2024-03-01-cover.png 200w, /static/d83a0d0e9897abf4dbc273a6793f03c7/a3397/2024-03-01-cover.png 400w, /static/d83a0d0e9897abf4dbc273a6793f03c7/a331c/2024-03-01-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Being busy is not something we strive for; it’s an axiom.</strong> Social media are not helping; our whole lives are on the plate. And we’re chefs serving fast food.</p> <p>We show how we dance on TikTok, we show where we go on Instagram, and we show how charming we are on Tinder. We show how intelligent and successful we are on LinkedIn.</p> <p><strong>We also show our engagement by checking Slack and work email after hours.</strong> And hey, are they really “after”?</p> <p>Of course, we’re doing none of that to show up. We’re casually elegant and casually busy.</p> <p><strong>Sometimes, all of that is not intentional; sometimes, it “just happens”, and that’s something to watch for.</strong> If we influence others, we may unintentionally boost their grind for business.</p> <p>You may say you have just 50 followers on Instagram and are not an influencer, so we’re good. You may be right, but… Maybe you’re a tech lead sending emails after hours or a friend sending work-related links to your colleagues. All of that, even if unintentional, can lead to building a false view of yourself as constantly busy. And you may create an impact that you don’t want to make.</p> <p><strong>I also should be more careful in what I’m posting and how I’m posting it.</strong> I’m not doing much overtime work now. One of the reasons why <a href="/en/leaving_event_store/">I went “solo”</a> was to get more time for family life, be more flexible and be less stressed. I was almost burned out in my last regular job. So, I know how hard it can be and how important it is to take care of mental health.</p> <p>Currently, I don’t have full-time assignments. Most of what I do nowadays is related to <a href="/en/training/">consulting and workshops</a>. My running joke is that I’m earning money from consulting for my OSS passion (so <a href="https://martendb.io/">Marten</a> and <a href="https://event-driven-io.github.io/emmett/getting-started.html">Emmett</a>).</p> <p>That gives me more time to work on OSS and content, as that’s also my job now. Visibility helps to get new contracts. So, I spend more time blogging and doing OSS than the average person. Doing stuff in public helps me to motivate. I like to learn and build in public and share my findings. Plus, it seems to help some people.</p> <p>So it’s not my intention to look busy. Yet…</p> <p><strong>Yet, it may look from the outside like I’m busier than I really am.</strong> And that’s dangerous as this can cause FOMO and make unhealthy comparisons.</p> <p><strong>So what to do about it?</strong> And what you can take from this blog?</p> <p>If we want to respond on Slack/Discord/Email in the evening, hoping that someone will read it in the morning, let’s not do that. Just answer in the morning or schedule the message to be sent later.</p> <p>If we want to share something, consider whether this message is worth it and if it has content besides showing ourselves as busy or grinding.</p> <p><strong>Let’s keep our overachieving freak on a leash.</strong></p> <p>Read also:</p> <ul> <li><a href="/en/a_few_words_about_workaholism/">Don’t be like Ebenezer Scrooge. A few words about workaholism</a></li> <li><a href="/en/it_doesnt_have_to_be_toxic_at_work/">It doesn’t have to be toxic at work</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Should you always keep streams short in Event Sourcing?]]>https://event-driven.io/en/should_you_always_keep_streams_short/https://event-driven.io/en/should_you_always_keep_streams_short/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/231b8733d083f3341835454dba48c1cb/a331c/2024-02-24-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAIAAABr+ngCAAAACXBIWXMAAC4jAAAuIwF4pT92AAADnklEQVQozwGTA2z8AEqQPlmYTWSfVoS3b2ScUl6PTluOSTFQI0xbNoulahNUEhthFytkHzprKz+AKm+jP2COQEJ6RSFCIwANAAA8hDRGhD43dDBLjj87djRKiT5vsVlJgT00QRx6fD8RTQ8QUxEcXhU1cyQxfCJUlTBOhjg5cCw1VCk0VjEAIWAcKV4kSINAZaFVR4g5OoEvRIc9OXszKDMXOk8iACUFBTAFACMAID8STHktXHszeotKiJtfjK1qjKxjACxqJDdwKzNpLF+gUCRhICRlIB1cGhVUFi88EjtNGQgXBQgfAyM7FCNDGDxRIXCBQIGNUpmlaIqZUoeWRwBuiklqhT4xVB01cigigCERTxQJHgkRJA44UDVWbk9GWDNkeENvfkWLolKWoFmdoVKfrGOXo2WFiFSJl1MAm7Fgj6VXjbVmgb1gg8lXV6I5R2sxSWg2OV09p7i0U3BNiJtelp1UnJ1VnaZejJ1cjJ5hgZhWfJZKfphNAJ+taZabXJWmbJipZJewZnPDV23HSGO+QWmqV87b1KC/hJq+bIalUIKbVIufWoCTSoCOR3iURGmELnOAPACCmkeImUiKnkuNnVOInFdMp1I4jzE3kR6EuXCLwX5/vVqUvW2XvGiHqVd9nVFwi0F0jklpkT1jgzlvkT4AfY5QdIM4mZtQl6Zgj55ZXI86RJ09SGs0YpRYD1sJR3wylJ9mjaNbhKNWiKBfeZhFaoc6a384Y403YH0uAGWnWoWeXJaaYpGhaH+qYnmqSUSgOmmgcHyedk6BOnSUXYyecmujU2ucTWeQUWmKTFt+NV17NlVuMVBnKQBJljtlrkl5l1Rokj9en0hrszp6sj6ZvoykvKGXs3SktoVjnlE0hiZKbitVaDlocUhSZzVUcTFFYidQciwAepJYYJFIaodHd5A3cJI6b6RIjrRNwsirncWkf7hrhLxwOJA5U4tBb4NRbYNGb4lIbZdEYJM0RHIhPF0hAIuWcmuZYUifSlKSNmqXPIGYXVaRPWeocXOcemWkSXOrWlSNPIGwYYSyXn6tXG6LUGaNRFSFMTVdHhk3CgBckltQnFkmmUEqjTpAo0iOnWxlflU3fzR1m4BynFx1pUdtpFBxrk2Fr1teoEhNeDdRWTY0VB4ZQAkGFgMASno+T3xHPYxCRZZIVp1Vjpl3mLt2o8h1o8KOYItZVp8yfMBRT5w6UZIrT5kqWHs5R1UrHkIMAxABBgwDDiFhYIPElsIAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/231b8733d083f3341835454dba48c1cb/a331c/2024-02-24-cover.png" srcset="/static/231b8733d083f3341835454dba48c1cb/36ca5/2024-02-24-cover.png 200w, /static/231b8733d083f3341835454dba48c1cb/a3397/2024-02-24-cover.png 400w, /static/231b8733d083f3341835454dba48c1cb/a331c/2024-02-24-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://event-driven.io/en/closing_the_books_in_practice/">In the last article</a> and <a href="https://www.eventstore.com/blog/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention">others</a> I did my best explaining why keeping streams short is important in Event Sourcing. I also showed you how.</strong> That should take you far enough, especially if you <a href="/en/a_few_words_on_communication/">talk to your business</a>. That will help you to find the lifecycle. But what if it doesn’t?</p> <p><strong>What if your entity doesn’t have such a lifecycle?</strong> Should we <em>artificially</em> find it for the sake of the specifics of the event model? Example? Personal or company data like names, addresses, etc.</p> <p>The question sounds simple, but the answer is, as always, nuanced. I have the rule that if I answer <em>“it depends”</em>, then I should follow it up with <em>“…on this and that”</em>. Let’s then discuss those <em>thises and thats</em>.</p> <h2 id="should-you-event-source-everything" style="position:relative;"><a href="#should-you-event-source-everything" aria-label="should you event source everything permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Should you event-source everything?</h2> <p>You can, but will that give you a lot of additional benefits? What could be the benefit of event sourcing company data change events? Quite often, those data are kept in the event store to have them for read model updates or use them as a state-carried transfer. Those are valid reasons, as they can add some event-driven flavour and reactive way of integrating some of those changes with the rest of the application.</p> <p><strong>Maybe, in this case, it’s better to use a state-first approach for those entities.</strong> What I mean by that is treating the state as the source of truth, using it to validate business rules. We can still record events together with updating the state.</p> <p><strong>Even if we append those events to the event store, we won’t be doing Event Sourcing, and that’s fine!</strong> It’s not Event Sourcing, as events are not a source of truth but are side effects of changing the state. Our main intention is not to record events but to change state and notify others about them. Logically, we’re not appending events but publishing them.</p> <p>We can still use event store for that and benefit from its publish-subscribe capabilities. We can also spare by adding yet another technology for that and spare ourselves another moving part.</p> <p>If you’re doing that, then you’re doing <a href="/en/event_streaming_is_not_event_sourcing/">Event Streaming, not Event Sourcing</a>. In that case, the length of the stream doesn’t matter so much, as you’re not reading from it to get the state; you just subscribe to its tail. We’re using an event store as a queue to forward events to subscriptions/read models.</p> <p>In Marten, it’s easier to keep consistency for that case than in EventStoreDB, as you can store the state as a document and append events in the same transaction.</p> <p>In EventStoreDB, you don’t have built-in read models, so you would need to use an additional database. You could use <a href="https://developers.eventstore.com/server/v23.10/projections.html#user-defined-projections">user-defined projection</a> and aggregate state from events. Then, you’d read the state from the last event in the projection stream. I saw one customer having 160,000,000 events in the stream, and the database survived. But please don’t do that. Projections are always run on the leader node, impacting the cluster’s performance. It’s also challenging to test them. If you want to do it, limit the maximum count of events.</p> <p>Ensure also that you’re not doing <a href="/en/state-obsession/">CRUD Sourcing</a> or <a href="/en/property-sourcing/">Property Sourcing</a>.</p> <h2 id="when-does-keeping-streams-longer-become-an-issue" style="position:relative;"><a href="#when-does-keeping-streams-longer-become-an-issue" aria-label="when does keeping streams longer become an issue permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>When does keeping streams longer become an issue?</h2> <p>If the stream doesn’t have a lot of events and performance is not critical here (<em>fameous last words</em>), then it may be okay to keep it living longer. That company details or address doesn’t change often. You probably won’t have a lot of events in such a stream. However, it may be living for a long time.</p> <p>Most of the considerations on keeping streams short relate to those that:</p> <ul> <li>have business lifecycles,</li> <li>are actively accessed,</li> <li>performance matters for their business case.</li> </ul> <p>The first point relates to understanding the business process, and the others relate to technical performance requirements.</p> <p>Company address or details won’t change too often, and performance is not critical, so it should be fine.</p> <h2 id="so-should-i-keep-those-streams-short-or-not" style="position:relative;"><a href="#so-should-i-keep-those-streams-short-or-not" aria-label="so should i keep those streams short or not permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>So, should I keep those streams short or not?</h2> <p>I think that it’s still worth setting some lifetime for our data and not trying to keep it forever. It’s about the data governance practices (but also storage size, which eventually may matter). So stuff that I described in the “GDPR series”:</p> <ul> <li><a href="/en/gdpr_for_busy_developers/">GDPR for busy developers</a></li> <li><a href="/en/gdpr_in_event_driven_architecture/">How to deal with privacy and GDPR in Event-Driven systems</a></li> </ul> <p>I wrote there that:</p> <blockquote> <p>Even for non-user data, most of us would have a challenge answering that, not even speaking about having policies. Yes, we don’t care much about data governance and privacy practices. The reality is we keep data until application logic removes it. We’re keeping all data because it may be useful in future, just in case. We’re starting to notice that when our database is overloaded or we don’t have space for backups.</p> </blockquote> <p><strong>Even though Company Data doesn’t have a clear business lifecycle, you don’t need to keep all the history of its updates for business logic.</strong> It’s still worth defining the data retention and archive strategy, e.g. once per year or another period, publish a summary event with the current data and truncate past events. That still allows you to rebuild your read models and get clear causality of processing.</p> <p>This strategy is also useful for cases where you actually don’t need this data anymore for 99% of situations. You keep it because you’re legally obliged to allow users to modify closed/removed/entities for the next few years. Then, instead of keeping all past data, it’s better to keep a single event in the stream and keep it alive by that. You can append a summary event after a defined period and archive the old data.</p> <p><strong>In the end, it’s your choice; you know your business process and system better.</strong> I suggest playing with different approaches and being flexible. You don’t need to use the same strategy for each business process. I hope this article gives you a bit more nuanced explanation of your options for more state-based processes.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Implementing Closing the Books pattern]]>https://event-driven.io/en/closing_the_books_in_practice/https://event-driven.io/en/closing_the_books_in_practice/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b812d413e0870f2b0b1519e0fdf1ea70/a331c/2024-02-17-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAC4jAAAuIwF4pT92AAADw0lEQVQ4yz2TS2hUdxjFb+mDgl34wMe+C+kLumjpSropVOiiIpRSKIJdWPFFKyoFidhJTTRRM8kkmZuZ+5j7fs+9k3FmEq2KaDWJpsVJYhKs4gNaKW21bSzBqr8yt9DF4Vv8+c53OOf8BT0ZRQ1qFK2YAdXh2IBEx9FeMp3dtLV3suOzLWxYt4533niVt15bm863X3+FN9e+zAfr1/Plvv20ZTroON5Hj6ggHOroItc3SEnR0BQdXdXRFA1D1enr6masmOVmpNG+fSuHv86Q7ewk192dQsz2okkyWlFGFguIuUGEvXu+om3/ATIHvyGX7Wcwl0cakikWVIrHehjPZrh8/AD1o4eQizrSkIIqtQ5bGCULXTWRRAlNMbA0G6Gnp59sdiCFmJcoBzH1aoNGtcFItc6FviOc2reNc7aGawcEto9nufiWSxzEVMIKZT+i7EW4poeQ68sz0D9Ef06kNztAfqCAXFCJvJCxS5e5PFojv2sLN6ammByfpBIlKAUZW7PwbZ/ACdLpmB66YiAURJnikIJUUCm0pqgShwm//HwPnjxlrtlEyfXz6OFDHj96xINffyNyA8R+EU3W0RUTTTZSG4qtUFpqFKlESdFRJC19mJ2a4Z/FxZRw4vxFztQa8PQpC3/8yeLCX4xf+I5cTw61WErJSrJOSTJRigaCoVlYho1jOeglE8fyuD4zzd35eXj8hJGkykgyzOLCAhPnzjE9don55lVKRRVd0bF0G7PUCshO9wXbdHBMB9fyMDUT3/G5e/06Z5OE23OzWJJK4gbcv/cTDdehoincnG5ia2ZaL1u3sEomhmpgtghd2/vf3Na14ShhdmIcv5BnbnKSnkNdxLbL7WvTJCUVpbuLG82rxH6IpVn/pW66KVzTRQjckNALKQflVOm3jVHuzF+j7hj8ONVk387dOLLK3MQ4Y6MjnInLzFy8wMlqLSWIvQjf8nANB8dwEEI/ooXIj3Btn2HTYNrM0zxzkr8f3GfzJ5tQB/P8fucW81euUDEMTrfvRTt2FM+NCGwvVZnC8hFaRHEYU4kqBF5EbJr80JdB7Orii63bef/d9zi4ew9tO3by6Ycb0Qsy3w8ewejN4rtRWqFWZ9M+tghr1QbVpMaJSoMTw/XUwwkpy+ebNvPi8y+xevlq1qxYxdIlS3lGeJYdW7Zhtx9AzQ8RtQT4IZUwJkl/TYKgikViz6fs+vi6iVoocqtis/fjj1i+bA2rV6xh+dKVrFy2iheeW8KujRs4eThD4vmcrjc4XatztjHCqWqVE2HIv+mxXgSTgrR1AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b812d413e0870f2b0b1519e0fdf1ea70/a331c/2024-02-17-cover.png" srcset="/static/b812d413e0870f2b0b1519e0fdf1ea70/36ca5/2024-02-17-cover.png 200w, /static/b812d413e0870f2b0b1519e0fdf1ea70/a3397/2024-02-17-cover.png 400w, /static/b812d413e0870f2b0b1519e0fdf1ea70/a331c/2024-02-17-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>No one knows who invented Event Sourcing. I overheard that <a href="https://www.investopedia.com/articles/financialcareers/09/ancient-accounting.asp">Hammurabi did</a>.</strong> Why? Because he standardised the first set of rules of accounting.</p> <p>Event Sourcing works as bookkeeping; we record new entries for each business operation. Not surprisingly, accounting patterns also work well in event sourcing.</p> <p><strong>For instance, we can use the “Closing the Books” pattern to model the lifecycle process effectively.</strong> <a href="https://courses.lumenlearning.com/sac-finaccounting/chapter/journalizing-and-posting-closing-entries/">Its name comes from the accounting domain</a>. All financial numbers are summarised and verified, and the final report is created at the end of each cycle (e.g., month and year). This data is used as a base for the next period. For accounting calculations, you don’t need to bring forward the entire history; it’s enough to summarise the crucial aspects to be carried forward, i.e. the opening balances, etc. The same pattern can be used for temporal modelling.</p> <p>I explained this pattern in detail in <a href="https://www.eventstore.com/blog/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention">Keep your streams short! Temporal modelling for fast reads and optimal data retention</a>.</p> <p>And the talk:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/gG6DGmYKk4I?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>Closing the Books is the essence of Event Sourcing modelling.</strong> Thanks to that, we can keep things short and thus run our system efficiently. We’re slicing the lifetime of our process, marking the start and end of the lifecycle using events.</p> <p>In this article, I assume you have read/watched, or skimmed the above resources. I’ll focus on the practical example using Marten. I know that’s not entirely to say <em>“Go read that, before we talk”</em>, but really: <em>“Go read that”</em>. It took me three months to write the article, and a bit less to prepare the talk. Of course, this article may be enough to grab the concept, but there you find more nuanced considerations.</p> <p><strong>Still, we’ll diverge from the accounting domain.</strong> I started explaining Event Sourcing like many of us: using a Bank Account example. <a href="/en/bank_account_event_sourcing/">But I stopped</a>. It’s a business domain that most of us believe in knowing how it works, but it’s much different from what we may expect.</p> <p>How much different? There’s no database transaction between multiple bank accounts while transferring money. A Bank Account is also not an entity or transactional boundary. In accounting, we add lifecycle and work on, e.g. Accounting Month.</p> <p>Yes, there’s no spoon.</p> <p><strong>Let’s use a scenario close to a financial domain but not the same, plus not tied to a specific time period: cashiers in stores</strong> (also used in the original article above). We could try to model that by keeping all transactions for the particular cash register on the same stream, but if we’re building a system for bigger department stores, then that could quickly escalate. We might end up with a stream containing thousands of events. That quickly could make our processing inefficient and unmanageable.</p> <p><strong>Yet, if we talked with our domain experts, we could realise this is not how our business works.</strong> All payments are registered by specific cashiers, and the cashiers care only about what has happened in their shift. They don’t need to know the entire history of transactions, just the starting amount of cash in the drawer (called <em>float</em>) that was left from the previous shift.</p> <p>Let’s try to reflect that in the simplified event model:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">CashierShiftEvent</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShiftOpened</span><span class="token punctuation">(</span> <span class="token class-name">CashierShiftId</span> CashierShiftId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> CashierId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Float<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> StartedAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShiftEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">TransactionRegistered</span><span class="token punctuation">(</span> <span class="token class-name">CashierShiftId</span> CashierShiftId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> TransactionId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Amount<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> RegisteredAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShiftEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShiftClosed</span><span class="token punctuation">(</span> <span class="token class-name">CashierShiftId</span> CashierShiftId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> DeclaredTender<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> OverageAmount<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> ShortageAmount<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> FinalFloat<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ClosedAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShiftEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">CashierShiftEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>So we have <em>ShiftOpened</em> and <em>ShiftClosed</em> events marking the lifetime of the cashier shift and <em>TransactionRegistered</em> to register the payments. Of course, we would have more types of events possibly happening between shift opening and closing, but let’s keep it simple. Just assume that <em>TransactionRegistered</em> is an example of the events you may have throughout the shift lifecycle.</p> <p><strong>You could notice <em>CashierShiftId</em> as a class, not a primitive value.</strong> It’s an example of the strongly-typed key:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CashierShiftId</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> CashRegisterId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> ShiftNumber<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">implicit</span> <span class="token keyword">operator</span> <span class="token keyword">string</span><span class="token punctuation">(</span><span class="token class-name">CashierShiftId</span> id<span class="token punctuation">)</span> <span class="token operator">=></span> id<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token interpolation-string"><span class="token string">$"urn:cashier_shift:</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">CashRegisterId</span><span class="token punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">ShiftNumber</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>I’m not a big fan of the ceremony brought by strongly typed key wrappers, but it makes perfect sense here. It’s not just a wrapper for a <em>Guid</em> or <em>string</em> value. Our cashier shift id is built from two components:</p> <ul> <li>cash register id,</li> <li>shift number.</li> </ul> <p>We’re using the <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Name">Uniform Resource Name structure (<em>URN</em>)</a>.</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">urn:cashier_shift:{CashRegisterId}:{ShiftNumber}</code></pre></div> <p>You can use any format, but URN is the standardised way for handling ids with meaningful segments. Why reinvent the wheel if we have a standard for it? Our URN starts with the prefix and stream type. Then, we have cash register id and shift number segments.</p> <p>We could add more components, e.g., date, if we’d like to have it sliced by a temporal aspect. So, having shift numbers reset every day. Most importantly, we’re defining the explicit lifetime for our cashier shift.</p> <p>If you are using EventStoreDB, you’d need to structure it a bit differently: <em>cashier_shift-{cashRegisterId}:{number}</em>. EventStoreDB expects the first part to represent the <em>category</em>, so for our stream type, we need to have a dash followed by the unique stream ID part.</p> <p>By default, Marten uses <em>Guid</em> as stream key, but <a href="https://martendb.io/events/configuration.html#stream-identity">it allows to use string</a>. To do that, we need to change that in the configuration:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Events<span class="token punctuation">.</span>StreamIdentity <span class="token operator">=</span> StreamIdentity<span class="token punctuation">.</span>AsString<span class="token punctuation">;</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Read also:</p> <ul> <li>we can use it to define the stream id representing cashier shift (read more in <a href="/en/event_stores_are_key_value_stores/">Event stores are key-value databases, and why that matters</a>),</li> <li>by that ensures uniqueness that there are no two shifts with the same number (read more in <a href="/en/uniqueness-in-event-sourcing/">How to ensure uniqueness in Event Sourcing</a>),</li> <li>use it also as a strongly-typed entity identifier if we really want (read more in <a href="/en/using_strongly_typed_ids_with_marten/">Using strongly-typed identifiers with Marten</a>)</li> </ul> <p>Moving on, our cashier shift can be modelled as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CashierShift</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">NonExisting</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShift</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Opened</span><span class="token punctuation">(</span> <span class="token class-name">CashierShiftId</span> ShiftId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Float <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShift</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Closed</span><span class="token punctuation">(</span> <span class="token class-name">CashierShiftId</span> ShiftId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> FinalFloat <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShift</span></span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">CashierShift</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s either non-existent when there are no shifts, open or closed. It’s trimmed to contain only information needed for decision-making (read more in <a href="/en/slim_your_entities_with_event_sourcing/">Slim your aggregates with Event Sourcing!</a>). We could build it from events as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CashierShift</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">public</span> <span class="token return-type class-name">CashierShift</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">CashierShiftEvent</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token keyword">switch</span> <span class="token punctuation">{</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token class-name">ShiftOpened</span> shiftOpened<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Opened</span><span class="token punctuation">(</span>shiftOpened<span class="token punctuation">.</span>CashierShiftId<span class="token punctuation">,</span> shiftOpened<span class="token punctuation">.</span>Float<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token class-name">Opened</span> state<span class="token punctuation">,</span> <span class="token class-name">TransactionRegistered</span> transactionRegistered<span class="token punctuation">)</span> <span class="token operator">=></span> state <span class="token keyword">with</span> <span class="token punctuation">{</span> Float <span class="token operator">=</span> state<span class="token punctuation">.</span>Float <span class="token operator">+</span> transactionRegistered<span class="token punctuation">.</span>Amount <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token class-name">Opened</span> state<span class="token punctuation">,</span> <span class="token class-name">ShiftClosed</span> shiftClosed<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Closed</span><span class="token punctuation">(</span>state<span class="token punctuation">.</span>ShiftId<span class="token punctuation">,</span> shiftClosed<span class="token punctuation">.</span>FinalFloat<span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Ok, let’s add the final building block: cash register setup. We need the cash register to have cashier shifts happening on it:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CashRegister</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">CashRegister</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">CashRegisterInitialized</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CashRegisterInitialized</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> CashRegisterId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> InitializedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">InitializeCashRegister</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> CashRegisterId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CashRegisterDecider</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token function">Decide</span><span class="token punctuation">(</span><span class="token class-name">InitializeCashRegister</span> command<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashRegisterInitialized</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Let’s now define the set of methods we’ll allow in our process. Accordingly, to events it’ll be: <em>OpenShift</em>, <em>RegisterTransaction</em>, <em>CloseShift</em>:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">CashierShiftCommand</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">OpenShift</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> CashRegisterId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> CashierId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShiftCommand</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RegisterTransaction</span><span class="token punctuation">(</span> <span class="token class-name">CashierShiftId</span> CashierShiftId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> TransactionId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Amount<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShiftCommand</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CloseShift</span><span class="token punctuation">(</span> <span class="token class-name">CashierShiftId</span> CashierShiftId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> DeclaredTender<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CashierShiftCommand</span></span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">CashierShiftCommand</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Our decision-making process will look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CashierShiftDecider</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token function">Decide</span><span class="token punctuation">(</span><span class="token class-name">CashierShiftCommand</span> command<span class="token punctuation">,</span> <span class="token class-name">CashierShift</span> state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span> <span class="token keyword">switch</span> <span class="token punctuation">{</span> <span class="token punctuation">(</span><span class="token class-name">OpenShift</span> open<span class="token punctuation">,</span> NonExisting<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShiftOpened</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShiftId</span><span class="token punctuation">(</span>open<span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> open<span class="token punctuation">.</span>CashierId<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> open<span class="token punctuation">.</span>Now <span class="token punctuation">)</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token class-name">OpenShift</span> open<span class="token punctuation">,</span> <span class="token class-name">Closed</span> closed<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShiftOpened</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShiftId</span><span class="token punctuation">(</span>open<span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">,</span> closed<span class="token punctuation">.</span>ShiftId<span class="token punctuation">.</span>ShiftNumber <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> open<span class="token punctuation">.</span>CashierId<span class="token punctuation">,</span> closed<span class="token punctuation">.</span>FinalFloat<span class="token punctuation">,</span> open<span class="token punctuation">.</span>Now <span class="token punctuation">)</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>OpenShift<span class="token punctuation">,</span> Opened<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token class-name">RegisterTransaction</span> registerTransaction<span class="token punctuation">,</span> <span class="token class-name">Opened</span> openShift<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TransactionRegistered</span><span class="token punctuation">(</span> openShift<span class="token punctuation">.</span>ShiftId<span class="token punctuation">,</span> registerTransaction<span class="token punctuation">.</span>TransactionId<span class="token punctuation">,</span> registerTransaction<span class="token punctuation">.</span>Amount<span class="token punctuation">,</span> registerTransaction<span class="token punctuation">.</span>Now <span class="token punctuation">)</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token class-name">CloseShift</span> close<span class="token punctuation">,</span> <span class="token class-name">Opened</span> openShift<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShiftClosed</span><span class="token punctuation">(</span> openShift<span class="token punctuation">.</span>ShiftId<span class="token punctuation">,</span> close<span class="token punctuation">.</span>DeclaredTender<span class="token punctuation">,</span> close<span class="token punctuation">.</span>DeclaredTender <span class="token operator">-</span> openShift<span class="token punctuation">.</span>Float<span class="token punctuation">,</span> openShift<span class="token punctuation">.</span>Float <span class="token operator">-</span> close<span class="token punctuation">.</span>DeclaredTender<span class="token punctuation">,</span> openShift<span class="token punctuation">.</span>Float<span class="token punctuation">,</span> close<span class="token punctuation">.</span>Now <span class="token punctuation">)</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>CloseShift<span class="token punctuation">,</span> Closed<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Cannot run </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">command<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name</span><span class="token punctuation">}</span></span><span class="token string"> on </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">state<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Yes, I like <a href="/en/my_journey_from_aggregates/">functional programming</a>, and I used new pattern-matching capabilities in C# here. If you’re not into it much yet, I’m passing the state and the command, and depending on its type, I’m running a specific business logic.</p> <ol> <li>Upon <em>OpenShift</em> command, I’m returning <em>ShiftOpened</em> event with the shift number equal to <em>0</em> when there was no state or incrementing the last shift number,</li> <li>If the shift is already opened, I’m not returning any events, making it irrelevant, so I am not making any changes and not throwing any exceptions.</li> <li>Registering transaction is just straightforward: adding a new event,</li> </ol> <ul> <li>I’m doing the necessary summaries upon the <em>CloseShift</em> command. I could also do here validation if the declared tender (so cash in drawer) is equal to the current float, but I didn’t want to blur the process,</li> </ul> <ol start="4"> <li>When trying to close an already closed shift, I’m making it idempotent accordingly as opening an already opened shift.</li> <li>If there was an invalid or unexpected combination of command and state, I just throw <em>InvalidOperationException</em>. Pattern matching allows me to spare some if statements.</li> </ol> <p><strong>The essential scenario happens here:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">(</span><span class="token class-name">OpenShift</span> open<span class="token punctuation">,</span> <span class="token class-name">Closed</span> closed<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShiftOpened</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShiftId</span><span class="token punctuation">(</span>open<span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">,</span> closed<span class="token punctuation">.</span>ShiftId<span class="token punctuation">.</span>ShiftNumber <span class="token operator">+</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> open<span class="token punctuation">.</span>CashierId<span class="token punctuation">,</span> closed<span class="token punctuation">.</span>FinalFloat<span class="token punctuation">,</span> open<span class="token punctuation">.</span>Now <span class="token punctuation">)</span> <span class="token punctuation">]</span><span class="token punctuation">,</span></code></pre></div> <p>Why is it essential? Let’s have a look at what our stream can look like.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/abfbecbb91d2f17b9a210d469f1e84bf/a331c/2024-02-17-streams.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACz0lEQVQoz12SS28bBQCE94dxQqoqKAhVINFLucChB+CCOKAeKpSIR3HTVoqKAmmlRqSihaqKU6eNcEz9iN92/EjsXXttr3f9XK+9L68fbemHGnriMJrRzFxGGsGdzrBsF9NymZgmhjmhM3EYOh762EbtDjEsD7U/wZ4+ZzB20QaTU6+h6QwNh46q0272sC0PoSWVqRaSSOU81UKA2MEqz25dILz5KYf+ddKP1ik/3SB8/WNK9y9T2PqSg58+QvT/SMh3nuytS6R8FwhdOUPDfw+hLVepneSpV8qIR4+JP/uehH+F+M4qxcNHNDJPqSV2KO6ukdi/jRi6SzrwM8fRP6j89StHgU3Kd1c53rxMMxhAMC0Hy7Kx3SV6v4jS2KfW1ZEHI3q6gdzqnM6T231qrS6DkYkktzEmDq12l07foKP2qVebjEcmgiafUD2KIRYylJIPiAZXiFx/h8jNdwk/WCF9/zsKuzdJrb1H8pfPKG19TuSHs5Qffsuh7xyJ9Ytkr35I7MpbiNs3EPpdjZYsoikKTTFEKX+HbOQe2fAWYimKKuZRKhmkxC7HmQOaxSjlxBPq5QT1XJBU9DEne9tU/txAiRwgGMYEY6QzHjv01Cx1cYeaIiOpMu12A7VRQx/0UZoyg46G1mow1BQGHRWtWUPvakycOc4crNlLhGa1QCERpJSOkYv+RtD/NSHf2wTXzrB/4zyJa++T3/6KuO8cyduXyG18QvTaBxz9/g2HV88Sv/MFncYJtuNhOR7CeNinpyn0NA21mUOq7FFM7VFKBxDzf1PJhJFKSaRsiGI+Qb0Yp5yNIlfyiLkIsXgIRVWwnTkT20OQug493Tw9sTt7iTt/hbsEZwHTJXgvwHvD08V/mff8tf6H2QuYLV/heEtM28O0ZwgpaYTaG9EbGljTBZa3wJrO/4cFznSO6y2Yzpan7Lzuum9yb4E9XWC6c/4F3+7z9x8j6m8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="streams" title="streams" src="/static/abfbecbb91d2f17b9a210d469f1e84bf/a331c/2024-02-17-streams.png" srcset="/static/abfbecbb91d2f17b9a210d469f1e84bf/36ca5/2024-02-17-streams.png 200w, /static/abfbecbb91d2f17b9a210d469f1e84bf/a3397/2024-02-17-streams.png 400w, /static/abfbecbb91d2f17b9a210d469f1e84bf/a331c/2024-02-17-streams.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>As you can see, the closed shift is a different stream than the one we’ll open. We’re setting that up in:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShiftId</span><span class="token punctuation">(</span> open<span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">,</span> closed<span class="token punctuation">.</span>ShiftId<span class="token punctuation">.</span>ShiftNumber <span class="token operator">+</span> <span class="token number">1</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Reminder that’s also reflected in our id structure:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">urn:cashier_shift:{CashRegisterId}:{ShiftNumber}</code></pre></div> <h2 id="closing-and-opening-shift-as-one-operation" style="position:relative;"><a href="#closing-and-opening-shift-as-one-operation" aria-label="closing and opening shift as one operation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Closing and opening shift as one operation</h2> <p><strong>Cool stuff, but how to effectively perform it?</strong> Let’s start with composing closing and opening:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token class-name">CommandResult</span> <span class="token operator">=</span> <span class="token class-name"><span class="token punctuation">(</span>CashierShiftId StreamId<span class="token punctuation">,</span> CashierShiftEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> Events<span class="token punctuation">)</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CloseAndOpenCommand</span><span class="token punctuation">(</span> <span class="token class-name">CashierShiftId</span> CashierShiftId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> CashierId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> DeclaredTender<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CloseAndOpenShift</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token punctuation">(</span>CommandResult<span class="token punctuation">,</span> CommandResult<span class="token punctuation">)</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">CloseAndOpenCommand</span> command<span class="token punctuation">,</span> <span class="token class-name">CashierShift</span> currentShift<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// close current shift</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>currentShiftId<span class="token punctuation">,</span> cashierId<span class="token punctuation">,</span> declaredTender<span class="token punctuation">,</span> now<span class="token punctuation">)</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> closingResult <span class="token operator">=</span> <span class="token function">Decide</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CloseShift</span><span class="token punctuation">(</span>currentShiftId<span class="token punctuation">,</span> declaredTender<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> currentShift<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// get new current shift state by applying the result event(s)</span> currentShift <span class="token operator">=</span> closingResult<span class="token punctuation">.</span><span class="token function">Aggregate</span><span class="token punctuation">(</span>currentShift<span class="token punctuation">,</span> <span class="token punctuation">(</span>current<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> current<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// open the next shift</span> <span class="token class-name"><span class="token keyword">var</span></span> openResult <span class="token operator">=</span> <span class="token function">Decide</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">OpenShift</span><span class="token punctuation">(</span>currentShiftId<span class="token punctuation">,</span> cashierId<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> currentShift<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// double check if it was actually opened</span> <span class="token class-name"><span class="token keyword">var</span></span> opened <span class="token operator">=</span> openResult<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">OfType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShiftOpened<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>opened <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Cannot open new shift!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// return both results with respective stream ids</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>currentShiftId<span class="token punctuation">,</span> closingResult<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>opened<span class="token punctuation">.</span>CashierShiftId<span class="token punctuation">,</span> openResult<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re running two commands sequentially, returning the results from both operations together with ids. Read also more in <a href="/en/simple_transactional_command_orchestration/">How to handle multiple commands in the same transaction</a>.</p> <p>If we’re using Marten, we can benefit from PostgreSQL transactional capabilities and <a href="https://martendb.io/documents/sessions.html#unit-of-work-mechanics">Marten built-in Unit of Work</a>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CloseAndOpenShift</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>CashierShiftId<span class="token punctuation">></span></span> <span class="token function">CloseAndOpenCashierShift</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">CloseAndOpenCommand</span> command<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> version<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> currentShift <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AggregateStreamAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CashierShift<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>CashierShiftId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">token</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShift<span class="token punctuation">.</span>NonExisting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>closingResult<span class="token punctuation">,</span> openResult<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token function">Handle</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> currentShift<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Append Closing result to the old stream</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>closingResult<span class="token punctuation">.</span>Events<span class="token punctuation">.</span>Length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>closingResult<span class="token punctuation">.</span>StreamId<span class="token punctuation">,</span> version<span class="token punctuation">,</span> closingResult<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">AsEnumerable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>openResult<span class="token punctuation">.</span>Events<span class="token punctuation">.</span>Length <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">StartStream</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CashierShift<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>openResult<span class="token punctuation">.</span>StreamId<span class="token punctuation">,</span> openResult<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">AsEnumerable</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> openResult<span class="token punctuation">.</span>StreamId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Thanks to the predictable structure of the id and calling the <em>StartStream</em> method, we’re ensuring that if the new shift has already been opened, then our operation will be rejected.</strong> We won’t have any duplicated shifts.</p> <p>We can use this code in the endpoint:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">app<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"/api/cash-registers/{cashRegisterId}/cashier-shifts/{shiftNumber:int}/close-and-open"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> cashRegisterId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> shiftNumber<span class="token punctuation">,</span> <span class="token class-name">CloseAndOpenShiftRequest</span> body<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromIfMatchHeader</span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span></span> eTag<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CloseAndOpenCommand</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShiftId</span><span class="token punctuation">(</span>cashRegisterId<span class="token punctuation">,</span> shiftNumber<span class="token punctuation">)</span><span class="token punctuation">,</span> body<span class="token punctuation">.</span>CashierId<span class="token punctuation">,</span> body<span class="token punctuation">.</span>DeclaredTender<span class="token punctuation">,</span> Now <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> openedCashierId <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">CloseAndOpenCashierShift</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> <span class="token function">ToExpectedVersion</span><span class="token punctuation">(</span>eTag<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Created</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"/api/cash-registers/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">cashRegisterId</span><span class="token punctuation">}</span></span><span class="token string">/cashier-shifts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">openedCashierId<span class="token punctuation">.</span>ShiftNumber</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> cashRegisterId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>As you can see, we greatly benefit from the repeatability and <a href="/en/how_to_effectively_compose_your_business_logic/">composability of the Event Sourcing and Decider pattern</a> and Marten’s transactional capabilities.</p> <p>We’re also using <a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">optimistic concurrency with ETag</a> to ensure we won’t face concurrency issues. Thanks to that, we will know we’re making decisions based on the expected state.</p> <h2 id="closing-and-opening-shifts-as-separate-operations" style="position:relative;"><a href="#closing-and-opening-shifts-as-separate-operations" aria-label="closing and opening shifts as separate operations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Closing and opening shifts as separate operations</h2> <p>The pattern of closing and opening as one operation can be suitable if our lifecycles are continuous. Yet, typically, they’re not. Occupancy of the cash registers by cashiers depends on the traffic. There may be periods when no one works on the particular cash register. How to handle that?</p> <p>Let’s tackle that step by step, starting from the end, so from the closing. It’ll be a simple operation now:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">app<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"/api/cash-registers/{cashRegisterId}/cashier-shifts/{shiftNumber:int}/close"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> cashRegisterId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> shiftNumber<span class="token punctuation">,</span> <span class="token class-name">CloseShiftRequest</span> body<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromIfMatchHeader</span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span></span> eTag<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> cashierShiftId <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShiftId</span><span class="token punctuation">(</span>cashRegisterId<span class="token punctuation">,</span> shiftNumber<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAndUpdate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CashierShift<span class="token punctuation">,</span> CashierShiftEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>cashierShiftId<span class="token punctuation">,</span> <span class="token function">ToExpectedVersion</span><span class="token punctuation">(</span>eTag<span class="token punctuation">)</span><span class="token punctuation">,</span> state <span class="token operator">=></span> <span class="token function">Decide</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CloseShift</span><span class="token punctuation">(</span>cashierShiftId<span class="token punctuation">,</span> body<span class="token punctuation">.</span>DeclaredTender<span class="token punctuation">,</span> Now<span class="token punctuation">)</span><span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>I’m using here a simple wrapper that will load events from the stream, build the state from it, run business logic and append event result events if there are such:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DocumentSessionExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">GetAndUpdate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> version<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">></span></span> handle<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token operator">=></span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">WriteToAggregate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> version<span class="token punctuation">,</span> stream <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token function">handle</span><span class="token punctuation">(</span>stream<span class="token punctuation">.</span>Aggregate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span>Length <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> stream<span class="token punctuation">.</span><span class="token function">AppendMany</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Cast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Opening will be more complicated.</strong> We need to get the shift number and data from the last closed one (like <em>float</em>, the state of the cash in the drawer after the last shift). Of course, the cashier could provide the previous shift number, but that’s error-prone and potential vulnerability. It’d be better if we track that on our own. And the best way for that is to build a model that would cache the information. We could define an updated projection based on the registered events.</p> <p>What should such a model contain? Potentially it could cache all needed information to open new shift, e.g.:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CashierShiftTracker</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">,</span> <span class="token comment">// Cash Register Id</span> <span class="token class-name"><span class="token keyword">string</span></span> LastClosedShiftNumber<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> LastClosedShiftFloat<span class="token punctuation">,</span> <span class="token comment">// (...) etc.</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That looks fine, as we could start by querying it and getting the needed information from it. Yet, in my opinion, that solution is not scalable and can provide some issues while maintaining. Our process may change, and we’ll need to update this projection each time that impacts closing or opening. That can create issues with versioning this model, updating it as it goes etc.</p> <p><strong>I think it’d be better to generalise it and make it agnostic to the specifics of our process.</strong> Closing the Books pattern is repeatable:</p> <ul> <li>Store summary of the closed streams needed for audit and for the next lifecycle period,</li> <li>When opening, get data from the last event in the closed lifecycle and use it to start a new period and stream.</li> </ul> <p>I wrote in <a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a> that Marten keeps the global sequence number. It’s a monotonic number. There may be gaps when an event is not added for some reason (e.g. a conflict or transient error). It’s unique for each event. We could use it as a reference to the closing event.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CashierShiftTracker</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">,</span> <span class="token comment">// Cash Register Id</span> <span class="token class-name"><span class="token keyword">long</span><span class="token punctuation">?</span></span> LastShiftClosedSequence <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Now, let’s define the multi-stream projection that will start tracking progress when the cash register is initialised and update it with each cashier shift closing event. We’ll get it from the event metadata (Marten’s <em>IEvent</em> wrapper).</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CashierShiftTrackerProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">MultiStreamProjection<span class="token punctuation">&lt;</span>CashierShiftTracker<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">CashierShiftTrackerProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CashRegisterInitialized<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CashierShiftEvent<span class="token punctuation">.</span>ShiftClosed<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CashierShiftId<span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">CashierShiftTracker</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">CashRegisterInitialized</span> initialized<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>initialized<span class="token punctuation">.</span>CashRegisterId<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">CashierShiftTracker</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>CashierShiftEvent<span class="token punctuation">.</span>ShiftClosed<span class="token punctuation">></span></span> closed<span class="token punctuation">,</span> <span class="token class-name">CashierShiftTracker</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> LastShiftClosedSequence <span class="token operator">=</span> closed<span class="token punctuation">.</span>Sequence <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We need to register it in the Marten configuration.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CashierShiftTrackerProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>The safest option for multi-stream projection is to register it as asynchronous.</strong> It will ensure that all events are run sequentially. Yet, it will introduce eventual consistency as it’ll be processed in the background. Getting the stale data is not dangerous. We’re using optimistic concurrency and have the uniqueness enforced by the predictable stream id structure. In the worst case, we may need to retry our handling.</p> <p>The other option is to register it as <em>inline</em>. Then, the projection will be updated in the same transaction as the appended event. This will also work for our case because there cannot be parallel shift closings for the particular cash register. Still, as a general rule, we should try to avoid it, as optimistic concurrency will only ensure concurrency on a specific single stream. We have multiple streams, so they could override each other when we have a high load.</p> <p>Pick your poison, depending on your use case.</p> <p>Let’s discuss using this model to locate the last cashier shift. We’ll define a dedicated method for that to encapsulate the locator logic:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">LastCashierShiftLocator</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>CashierShift<span class="token punctuation">></span></span> <span class="token function">GetLastCashierShift</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> cashRegisterId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> tracker <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LoadAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CashierShiftTracker<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>cashRegisterId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>tracker <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Unknown cash register!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> lastClosedShiftEvent <span class="token operator">=</span> tracker<span class="token punctuation">.</span>LastShiftClosedSequence<span class="token punctuation">.</span>HasValue <span class="token punctuation">?</span> <span class="token punctuation">(</span><span class="token keyword">await</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">QueryAllRawEvents</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">SingleAsync</span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>Sequence <span class="token operator">==</span> tracker<span class="token punctuation">.</span>LastShiftClosedSequence<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Data <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token keyword">return</span> lastClosedShiftEvent <span class="token keyword">is</span> <span class="token class-name">ShiftClosed</span> closed <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShift<span class="token punctuation">.</span>Closed</span><span class="token punctuation">(</span>closed<span class="token punctuation">.</span>CashierShiftId<span class="token punctuation">,</span> closed<span class="token punctuation">.</span>FinalFloat<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashierShift<span class="token punctuation">.</span>NonExisting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As explained above, we’re getting the tracking information, loading the last event (if there’s such) and returning the <em>ClosedShift</em> or <em>NonExisting</em> if no shift was closed yet.</p> <p>Such tracking logic is repeatable and could even be generalised to other scenarios if needed.</p> <p>Having that, we can define the endpoint for opening the cashier shift as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">app<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"/api/cash-registers/{cashRegisterId}/cashier-shifts"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> cashRegisterId<span class="token punctuation">,</span> <span class="token class-name">OpenShiftRequest</span> body<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> lastClosedShift <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">GetLastCashierShift</span><span class="token punctuation">(</span>cashRegisterId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token function">Decide</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">OpenShift</span><span class="token punctuation">(</span>cashRegisterId<span class="token punctuation">,</span> body<span class="token punctuation">.</span>CashierId<span class="token punctuation">,</span> Now<span class="token punctuation">)</span><span class="token punctuation">,</span> lastClosedShift<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> opened <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">OfType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShiftOpened<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>opened <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Cannot Open Shift"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CashierShift<span class="token punctuation">,</span> CashierShiftEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>opened<span class="token punctuation">.</span>CashierShiftId<span class="token punctuation">,</span> result<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Created</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"/api/cash-registers/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">cashRegisterId</span><span class="token punctuation">}</span></span><span class="token string">/cashier-shifts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">opened<span class="token punctuation">.</span>CashierShiftId<span class="token punctuation">.</span>ShiftNumber</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> cashRegisterId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re using a simple wrapper for appending new events similar to <em>GetAndUpdate</em> shown before.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DocumentSessionExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> id<span class="token punctuation">,</span> <span class="token class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> events<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>events<span class="token punctuation">.</span>Length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> Task<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">;</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">StartStream</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> result<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Cast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> documentSession<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">token</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>Keeping streams short is the most important modelling practice in Event Sourcing. Closing the Books pattern is the biggest enabler for that. I hope that after this article, you’ll know how to implement that effectively using Marten.</p> <p>There are other options to do it, that I described in <a href="https://www.eventstore.com/blog/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention">Keep your streams short! Temporal modelling for fast reads and optimal data retention</a> like:</p> <ul> <li>opening periods asynchronously,</li> <li>There may be scenarios where you have multiple open shifts (e.g. cash register in the restaurant used by multiple waiters),</li> <li>you might not be able to get (or even want to have) predictable ids.</li> </ul> <p>Those scenarios will require different ways to handle that, but I’m sure the techniques described above can be enough for you to implement that.</p> <p>See the full code in my <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/ClosingTheBooks">sample repository</a>.</p> <p>Most importantly: talk with the business experts and ask enough whys to understand the lifecycle of your business use case. Listen for the keywords like open/close/end, summary, daily, monthly, etc. For business experts, lifecycle may be so apparent that they won’t mention it straight away, but if you dig and ask enough questions, they’re typically more than happy to reveal it. Workshops like Event Storming can help you with that.</p> <p><strong>Check also the follow up article for more nuanced considerations on <a href="/en/should_you_always_keep_streams_short/">Should you always keep streams short in Event Sourcing?</a>.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Announcing Emmett! Take your event-driven applications back to the future!]]>https://event-driven.io/en/introducing_emmett/https://event-driven.io/en/introducing_emmett/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c97294c9e971f829961393908b28cd60/a331c/2024-02-11-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAC4jAAAuIwF4pT92AAACCElEQVQ4y82Uy24TMRiF5wWQwqVz8VwzzWUml0natIUKpJZCk5SQ0jR3KqpmQRQWlEsXwIY9T8CSBRtWsAYqVohH4HU+5CmpUCuEVIHE4ui3f9vHPue3rZimy9+E8o8IvRNwEcI5G6FcqKoamqofQTNOTfqV/LcbiZ+EjpOisrhBVFqlVL5OMVpB1200zURVTQzDPkWmCxtVWGimjapbqMLGMO14XPH9gO3OhPXqXRrNEbeb98hm8wRBnkKhhOv6MfmUTMaUlSWyckQioJzKEzkhvhHgiiSK56Xodrbp9zs0btVpt1sMh0N6vR7j8X2q1RqO45NMpjCFw0VhceCv8TF6wLu5x3wdH/BlvcULdYdlaw7FdWfp9wdsbt5hMBzG7WZzk3a7Ta1Wp9VqMRrtsbx8FU010YRJPWzx+doj3ixM+LC2x9vaE56m97miFyShT7fbjU+1u7tLFJXRdetY3lSq9NIyPWYMQTXY4rD+nG/7LzncmPD92Ss+vX5PaaaAYttJ5ueXqFSWKJcrBEGRdDpHGBYJggKZTJ5MJkc6HWLFBXFYdDNsl1bZWblBY+Em/YXLPBx0yHohimV5hGFEMplGCDf2S0JaIeE4s8e56fWQVT2vGSQu6VxQdRKq4FxiJq5+fA+lRClpKlFC9k/mYphHFtimh215sQ0ySqVnenp/ekHKf/85/ACz6bcNPM+L2gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/c97294c9e971f829961393908b28cd60/a331c/2024-02-11-cover.png" srcset="/static/c97294c9e971f829961393908b28cd60/36ca5/2024-02-11-cover.png 200w, /static/c97294c9e971f829961393908b28cd60/a3397/2024-02-11-cover.png 400w, /static/c97294c9e971f829961393908b28cd60/a331c/2024-02-11-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Simple is not easy. Each person has its definition of it.</strong> For me, it means that when I look at the solution, I think: <em>“So simple. Why didn’t I come up with it?”</em>. That also means that it’s straightforward to understand.</p> <p><strong>In recent years, I went down the rabbit hole trying to explain to you and other readers that Event Sourcing and CQRS can be effective and simple.</strong> I wrote hundreds of articles, delivered samples in multiple languages and worked on tooling like Marten and EventStoreDB. I’m also doing <a href="/en/training">workshops and consultancy</a>.</p> <p><strong>Why do I do it?</strong> I don’t want to be a content creator or influencer. That’s not my goal. I just saw in my projects that Event Sourcing and CQRS are working and helping to build better applications. If applied in a simple way, they not only do not bring additional complexity but are cutting it.</p> <p>Again, simple is not easy. But why?</p> <p>Because it requires a lot of iterations and experience, it’s hard to learn from other people’s mistakes. Reading blogs is kinda like trying to do it. Thanks to that, you can avoid some, but not all. Wouldn’t it be nice to be walked through by hand sometimes?</p> <p>Event Sourcing in CQRS having the guidance is easier than in other approaches. They have common and repeatable patterns like:</p> <ul> <li>you read events and build the state from them,</li> <li>you take this state and command and run business logic on them returning event or multiple events,</li> <li>you append those events to the event stream,</li> <li>rinse, repeat.</li> </ul> <p>But well, you can always forget about it, or your colleague.</p> <p><strong>That’s why I decided to release <a href="https://event-driven-io.github.io/emmett/">Emmett</a>.</strong> I gathered a catalogue of Node.js and TypeScript patterns in my blog and <a href="https://github.com/oskardudycz/EventSourcing.NodeJS">sample repository</a>.</p> <p>You can already use it by calling:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">npm</span> <span class="token function">add</span> @event-driven-io/emmett</code></pre></div> <p>It already has the first set of documentation: <a href="https://event-driven-io.github.io/emmett/">check here</a>.</p> <p><strong>Why Node.js and TypeScript?</strong></p> <p>For me, it’s the environment I feel the most effective in. I like its minimalistic approach and flexibility, plus TypeScript is an excellent language with its shapeshifter capabilities. It doesn’t require much boilerplate; it cuts the cognitive load, so it aligns with my current vision of building applications.</p> <p><strong>Why Emmett?</strong></p> <p><a href="https://en.m.wikipedia.org/wiki/Emmett_Brown">Because I want to take your event-driven applications back to the future!</a>.</p> <p><strong>Is it production-ready?</strong></p> <p>Kinda. What is here is already usable, but you’ll need to wait for the full production experience in all essential aspects.</p> <p><strong>What features does it have?</strong></p> <p>Essential building blocks for designing and running business and application logic like:</p> <ul> <li>typings around events, commands, Deciders, Workflows, etc.</li> <li>command handling wrappers for the application layer,</li> <li>basic, in-memory event store implementation.</li> </ul> <p><strong>What features will it have?</strong></p> <p>We’ll see, but I’d like to have the following at some point:</p> <ul> <li>building blocks for the Web Apis with Event Sourcing and CQRS,</li> <li>implementation of event store using EventStoreDB, PostgreSQL, SQLite, etc.</li> <li>abstractions for building read models,</li> <li>building blocks for integration and running distributed processes,</li> <li>built-in open telemetry,</li> <li>GraphQL API for event stores,</li> <li>Full stack development helpers with Next.js or HTMX,</li> <li>running it serverless or on the web with SQLite,</li> <li>streaming data through HTTP API (and enabling integration scenarios through it).</li> <li>defining event transformations and projections with WebAssembly,</li> <li>etc.</li> </ul> <p><strong>Would it be a competitor to other stores?</strong></p> <p>Probably not. For now, I’d like to have a safe playground to have fun, experiment and try out new ideas. Still, I expect what I deliver to be safe to use in production.</p> <p><strong>Why there’s no license?</strong></p> <p>Because I’m unsure how this will end, and I don’t want to expose it as an MIT license from the beginning.</p> <p><strong><a href="https://event-driven-io.github.io/emmett/getting-started.html">So try it on your own, have fun and send me feedback!</a></strong></p> <p><a href="https://discord.gg/fTpqUTMmVa">Join our Discord</a> to be a part of building it!</p> <p>See also a follow up articles on Emmett:</p> <ul> <li><a href="">Testing Event Sourcing, Emmett edition</a>.</li> </ul> <p>Read also more about Node.js and Event Sourcing to get what I want to provide out of the box:</p> <ul> <li><a href="/en/type_script_node_Js_event_sourcing/">Straightforward Event Sourcing with TypeScript and NodeJS</a></li> <li><a href="/en/how_to_have_fun_with_typescript_and_workflow/">How TypeScript can help in modelling business workflows</a></li> <li><a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a></li> <li><a href="/en/testing_event_driven_projections/">How to test event-driven projections</a></li> <li><a href="/en/how_to_tackle_esmodules_compatibility_issues">How to tackle compatibility issues in ECMA Script modules (and in general)</a></li> <li><a href="/en/introduction_to_event_sourcing/">Introduction to Event Sourcing - Self Paced Kit</a></li> <li><a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">Dealing with Eventual Consistency and Idempotency in MongoDB projections</a></li> <li><a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">How to use ETag header for optimistic concurrency</a></li> <li><a href="/en/long_polling_and_eventual_consistency/">Long-polling, how to make our async API synchronous</a></li> <li><a href="/en/fun_with_json_serialisation/">Fun with serial JSON</a></li> <li><a href="/en/structural_typing_in_type_script/">Structural Typing in TypeScript</a></li> <li><a href="/en/partial_typescript/">Why Partial<Type> is an extremely useful TypeScript feature?</a></li> <li><a href="/en/how_events_can_help_on_making_state_based_approach_efficient/">How events can help in making the state-based approach efficient</a></li> <li><a href="/my_journey_from_aggregates/">My journey from Aggregates to Functional Composition</a></li> </ul> <p>Cheers.</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Production-Grade Event Sourcing Workshop - Modelling, DevOps, Process]]>https://event-driven.io/en/production_grade_event_sourcing/https://event-driven.io/en/production_grade_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/79af8a34fbba203ee6f1641cdb9172a9/a331c/2024-02-04-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAPCAYAAADkmO9VAAAACXBIWXMAAC4jAAAuIwF4pT92AAAENklEQVQ4yx3P2U/TBwDA8V/2oC4xc9PMTI14IEZxeLF5TlSGKAqIyDFhrIotFhhgQaBASwXEVqjlKBQExEILpQctl7TScovncBoyER92JdM9LcuSLctevsv2D3ySj6ArkvDQU838N01YDLmoci6gKc7ielEW2tIczE2l3K5VoCsvQHethPrqMloab3CnrRaTUU9XVzM9Pe04nCaGhu0I0uQoskXRVGacRZN2GlHEfsRxYShlF1DILqLMS6O8OBtNeSG12lKaDGru3K7Bam2hr/8/pIcht5XBYQsjoy6EXVvXI0s4xoAyldm6Ql60XMWuzqYoJ5kyuRiNKhPdtSvoq0toNZRjMt7E1mNgsM+Ix23B67Mz4rPj8drxjQ8gJMcco/7rBCyFIh7oS/jRWsNbdyv6cilahZjGymyaq/Jpq1PQeasCW6eOfmsjnv52fJ4uxsfsjE+4GJscZHLGh6ARx9KaHcfNjAQ6i8QMXL+Mo6oAi05GU6UUoy4Xs15Ot6EUR7uaPrOOgZ4G3M5WJu5ZmBzvZWr6LvefTDD5eBqhNj0Rq1zEtEZGb14K05orvOy4wVBdLiZtBkNtJbiNZfi6Nfh6dHht9Yy5mpjxdODsqMXtNDLudeEwtzLz7CmC5OReLMVifNcv03AhnuGrucybtWiy4igWneBWYRLSs4cZNVUw21/PwlgnXnMVjUoJNRln0cslVFy5RNLxUDq6uxGOBqygSnIGo0KCI1/KQ4OalpJU5DE7qE/ZTUF0MIcD1xK+LxBvexkeQz5NBWIyoyMwZMTgqcrCVprG8eBA9IWZCLHhIdwsySU9OoQbGSm0KHOIPBhEsP+HhAStw2/lMjZ+9AGnQ4OpyT9H4/kwBktSqb8UhbNSyh8+I448ES3psQzUlSH0KAu4dbub+spytDmp9NeoEH0Rz6J3l7B40WLWrlzGTv8VbA9YTXFkMDmHglDFhTGlFvOqOQVn8TmiDnyKLPIIaYnxCA55LiZVKXrXJHdHJmhQyem1u3AN3eNE6EGC1i8nap8/x7auQnJ4G5E7/QkNWEPWgd0EbtjAe8vXsG61H6L9+/DfsBnB2OXCd7WINqONVs8s6uoG7ntG+eX3fzB2Wf6v6y4ewpJ+gIjtazkVvInPA/3IOxiAKmEvKaEfk34mBHVaApeSYhHafc+Yamll1tiOeWqOjr5RXk1M8t0Pv2Jzj6P96hNwnOPvznimFSH05uynInEXzryj/GVK4mX1Kaok4Shl56lTZSJYp+exjTzlp+4Ohqee0/tgnjnvKE9ev2H029fEhOxAFbMF/flgHJc/43FFOE8qI5jTRvOzIY4/Tcn8dudLvrcrWHCqEdwv3tA59ZpH3VZmB4axP1hgrs/Fo4W3DM48Z8um9WzzW877S5fwzuKlbPZbyd4tq9i5aQ3RezaSGrYVZeIemqVHsOad5F9Cqu+z6i4wyQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/79af8a34fbba203ee6f1641cdb9172a9/a331c/2024-02-04-cover.png" srcset="/static/79af8a34fbba203ee6f1641cdb9172a9/36ca5/2024-02-04-cover.png 200w, /static/79af8a34fbba203ee6f1641cdb9172a9/a3397/2024-02-04-cover.png 400w, /static/79af8a34fbba203ee6f1641cdb9172a9/a331c/2024-02-04-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Putting the career bets is not easy; I made boring and pragmatic choices for most of my career. They took me far.</strong> Yet, I always felt that something was missing and that my systems could be delivered and operated better. I tried many things; I even finished my post-graduate studies around project management.</p> <p><strong>All of that helped me make an impact, but the most significant A-ha moment for me was Event Sourcing. Why?</strong></p> <p>It helps to create the synergy between business and development. Thanks to that, we can get the fastest feedback loop, streamline development and operate with more insights and data correlation. We’re investing in the quality of the information in our system without losing much.</p> <p><strong>Event sourcing is already battle-tested by both big and small companies.</strong> They see the advantages it brings, e.g. business focus and keeping all information about the business process. It can also be a great input to analytics, providing increased diagnostics and tracing. Nowadays, that’s essential for running a system on the scale.</p> <p>There are already decent materials teaching how to start the journey with Event Sourcing. Hell, I wrote over 100 of them on this blog. Yet it’s not easy to get certainty that our design will work on production. Even when we get there, it’s worth revisiting lessons learned and discovering what we can improve in preparing for the upcoming challenges.</p> <p>That’s why I’m explaining the nuances, strategies, tips, and tricks of having your event-sourced system well-architected. We need more of that!</p> <p>I want to invite you to my biggest initiative so far!</p> <h2 id="a-three-day-workshop-on-production-grade-event-sourcing-modelling-devops-process" style="position:relative;"><a href="#a-three-day-workshop-on-production-grade-event-sourcing-modelling-devops-process" aria-label="a three day workshop on production grade event sourcing modelling devops process permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>A three-day workshop on Production-Grade Event Sourcing: Modelling, DevOps, Process</h2> <p>The goal of this workshop is to strengthen all of the critical aspects of running event-sourced on production:</p> <ul> <li>modelling (like keeping streams short, managing consistency and efficiently handling business logic, best practices and anti-patterns),</li> <li>managing schema evolution, so events versioning,</li> <li>advanced projections design and resiliency, so how to get the best from events to efficiently get read models and insights from them,</li> <li>DevOps techniques (like traceability, blue-green projection rebuilt, archiving old data),</li> <li>handling distributed processes and integrating with other systems,</li> <li>putting event sourcing as a vital part of the whole architecture (so how to integrate it with non-event-sourced modules, defining private and public schema).</li> </ul> <p><strong>The workshop will happen <a href="https://ddd.academy/production-grade-event-sourcing/">May 27-29, 2024, as part of Domain Driven Design Europe</a></strong>. Do you know how beautiful Amsterdam is at that time of the year? You better check that!</p> <p><strong><a href="https://ddd.academy/production-grade-event-sourcing/">You can sign up through the workshop page.</a></strong></p> <h2 id="what-will-you-learn" style="position:relative;"><a href="#what-will-you-learn" aria-label="what will you learn permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What will you learn</h2> <p>After the workshop, you’ll understand and practice techniques that will allow you to run your Event-Sourcing system on production. All the exercises will be backed by the versatile knowledge I gained through my projects and working with my clients.</p> <p>I’ll share my experience building systems like Marten and EventStoreDB and working as an architect and consultant in many Event Sourcing projects.</p> <p>We’ll start with the project of the system that looks fine as the first production deployment. Through group exercises, a mixture of modelling and practical coding exercises, we’ll analyse potential issues step by step. We’ll learn how to refactor it step by step without breaking it. That will allow us to fix the boundaries, consistency guarantees, and resiliency. We’ll practice both adding new capabilities and changing existing ones. We’ll practice schema evolution and DevOps practices like projection rebuilds. We’ll end with a more robust and prepared-for-the-next-challenges system.</p> <h2 id="prerequisites" style="position:relative;"><a href="#prerequisites" aria-label="prerequisites permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Prerequisites</h2> <p>The workshop is designed for people who already have experience with Event Sourcing. Especially for those who would like to assess the production-readiness of their design or already deployed system. We won’t start from basics but assume:</p> <ul> <li>experience in how Event Sourcing works (storing events, building state from them),</li> <li>knowledge of how to model business logic and projections/read models,</li> <li>Understand that event sourcing is not event streaming and why streaming solutions like Kafka and Pulsar should not be used as event stores.</li> </ul> <p>I assume you know modelling techniques like EventStorming, C4 and modularity concepts (microservices, monolith, CQRS, etc.). And that you were already building and designing web applications. You don’t need to be an expert in that, but the more you know the easier it will be.</p> <p>Most of the exercises will be focused on general understanding and will not be technology-specific, but practical will require knowledge of one of the languages: C#, Java, or TypeScript. We’ll use Marten and EventStoreDB as event stores examples.</p> <h2 id="what-if-you-dont-have-event-sourcing-experience-yet" style="position:relative;"><a href="#what-if-you-dont-have-event-sourcing-experience-yet" aria-label="what if you dont have event sourcing experience yet permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What if you don’t have Event Sourcing experience yet?</h2> <p>I’ve got you covered! Check my <a href="/en/introduction_to_event_sourcing/">self-paced kit</a>.</p> <p>See the playlist with my talks:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/jnDchr5eabI?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>Of course, that’s not the full experience of doing a <a href="/en/training/">workshop</a>, but it can be a good starting point.</p> <p><strong>If you need more than that and want full coverage with all nuances of the private workshop, check <a href="/en/training/">the training page</a>. Feel invited to contact me via <a href="mailto:[email protected]">email</a>. I’m happy to help you via consultancy or paid mentoring!</strong></p> <p>If you’re not persuaded enough, check <a href="https://www.linkedin.com/in/oskardudycz/">recommendations from my previous clients on my LinkedIn profile.</a>.</p> <p>Here are some of them:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 776px; height: auto" > <a class="gatsby-resp-image-link" href="/static/cd452e69087162f0af1f6d189918da1a/3bb8e/2024-02-04-reference1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 58.50000000000001%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAYAAABiDJ37AAAACXBIWXMAAAsTAAALEwEAmpwYAAAB20lEQVQoz41T226bUBDkD/LQl7iJbYyxud85YAwYgi+JVFXqT7RV2/9/nmoW49RSH/Iw2mU5Z9nZGbSf339gOL0iURXyXY2yPsCLMuimhdl8hc8LQ/C0XH8I2p9fv/Hl6zfsmh51N6AbLqjaHnFeYm15MLYu5qvNXfPZhH9r8xVmcwPaw6dHmLYPP85g+5HADRLopo3VxhEsjS0W/8Fybd3lhPa0MGB5EYqqRbk/IC9rZGWNYt8iK/dQVYOq6QU8I3HfQjFve7lDsG77MTSOunXDsVExNkiLSnaqdg3CtJCpLS+E5YYyvRemMgSfR1axrOfxWYdG/tyTEyTyYkQkH3HDBBsnENpcAc8Rsoa1BWPr3L0TyuzqRamIUR8GtP0Rh5eTCEQ0/RHd8YKmO6I/vWI4v8lZ1rrhLDXmL+c3uS+UOVV/vCBRO0RpgSBRiLMSQZwjTJQ8h6kS4aSeKERZKbVb5L04HylzZDZlnJSjVSblptrKtG9n9GuuX/Nn3Rx3OIlClWhsikMxqCjVpNFZY2SdE6aqEgHJiJg8y17SkAvl2PxDKA6pBclIVyhJLITFKMa7MJyamLwqDWkFLjQt9jev0TpUmKYnyEKs40Vwr7ZhndHxY2ydABvHx1+ULm9L5OQL6gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="reference 1" title="reference 1" src="/static/cd452e69087162f0af1f6d189918da1a/3bb8e/2024-02-04-reference1.png" srcset="/static/cd452e69087162f0af1f6d189918da1a/36ca5/2024-02-04-reference1.png 200w, /static/cd452e69087162f0af1f6d189918da1a/a3397/2024-02-04-reference1.png 400w, /static/cd452e69087162f0af1f6d189918da1a/3bb8e/2024-02-04-reference1.png 776w" sizes="(max-width: 776px) 100vw, 776px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 778px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c0a2efc77f918fe1cc0163f7535c8dbd/56544/2024-02-04-reference2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 30.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABEElEQVQY03WQu1LDQAxF/QFUVIE4tuPEbzuOnYez3vX6AVS0DA0VBQMz1DR0fPllJI9TJcUdjaTdqyMZf78/+P78QJAW2FcS8aaE5fownTUWS49F+STK7y0Xc3t1UcbX+yveXp6xExqi6XCSLRuXB4HiIJBudwiSHFG6RZQV8KIUd4slm16ScXM7w9zxkZdH+PEGYZojzgrYqwCW652JJhEF0V+TYdorrIMER9GctT9JjkR7rMdaJTXrIBSEalHTNqrlGr2TukeY5DAIn8hUO0CoDlIPvHrd9BzZWLXcozdkoLoHzqkv24HPQ2dhQjL0whS6f4LuH0cz3fOHqh6nkwnnUo+9aaAaKb0ow8x0+Ib/JLq7TNkX/l4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="reference 2" title="reference 2" src="/static/c0a2efc77f918fe1cc0163f7535c8dbd/56544/2024-02-04-reference2.png" srcset="/static/c0a2efc77f918fe1cc0163f7535c8dbd/36ca5/2024-02-04-reference2.png 200w, /static/c0a2efc77f918fe1cc0163f7535c8dbd/a3397/2024-02-04-reference2.png 400w, /static/c0a2efc77f918fe1cc0163f7535c8dbd/56544/2024-02-04-reference2.png 778w" sizes="(max-width: 778px) 100vw, 778px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 778px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ca9d9f131760f9810cf60309a3f99d18/56544/2024-02-04-reference3.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 30.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAGCAYAAADDl76dAAAACXBIWXMAAAsTAAALEwEAmpwYAAABEUlEQVQY01VRy1KDQBDkD7x4SDSQAAbCAgsEebO8KuXRskx51PKai/9/a2sGiXro2t7pmZ6ZXe3rcsHn+wfCJENWtth7ITa7B+jmnmFYDmNru//4Arrfb+0rtNfzG55fzsiqFlU74LFoGFFaQB4zeGHCTej0ghiOkDj4EQ5BDCFJC7A2rF/Dm9sVdNNBlOac7IUxRJhg99Odpr0zrCuWwr+ccta6yTGNArYrULY98kohrxWvXqmBedF0s1Yr3oC0sul5I4oRSBfyyMbaamPCFRJtP6HpJhbJjDijn2NkQpwaqOEENZ7mPK4b0U9P/A8ajeqIkAXqXtTzRIsJFRHIiLS6GxGnBVw/4nd1fckDLZ/5DTxXuzEOTCmlAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="reference 3" title="reference 3" src="/static/ca9d9f131760f9810cf60309a3f99d18/56544/2024-02-04-reference3.png" srcset="/static/ca9d9f131760f9810cf60309a3f99d18/36ca5/2024-02-04-reference3.png 200w, /static/ca9d9f131760f9810cf60309a3f99d18/a3397/2024-02-04-reference3.png 400w, /static/ca9d9f131760f9810cf60309a3f99d18/56544/2024-02-04-reference3.png 778w" sizes="(max-width: 778px) 100vw, 778px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://ddd.academy/production-grade-event-sourcing/">Again, feel invited to sign up for the workshop.</a>. And <a href="mailto:[email protected]">contact me</a> if you have any questions.</strong></p> <p>See you in Amsterdam!</p> <p>Cheers.</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How TypeScript can help in modelling business workflows]]>https://event-driven.io/en/how_to_have_fun_with_typescript_and_workflow/https://event-driven.io/en/how_to_have_fun_with_typescript_and_workflow/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/19588ef9936c3cd565b75a60849c06fb/e4699/2024-01-28-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB60lEQVQoz42SyW4TQRRF/TNs+BA28CEgZYEECAnBNgukWCCEEgYpUQA5IDvGid3t9FBDDx6I7R6qHcPXHNRux7HDhsXVk0qvzru36tW8XOOZSn5eSZTVREtJE+KZeHl2W9f93oZqt4EiV/h5QJhYRKmFl7iMZy1ELjdgatX3H0BZaDq/JL3jZ+T+Lp9P9kmaO6jUxsk00gSIIsbPw/WANdBcA3ONk0mcVNId9+mOLFqySTdu0xt1aQdt3MQhuDzFSzzU5IwwtfEyhZfKrfhrh24icBJBd9jnfGAR/YnxFzGi0OhFxEHjA9npY35Yx3Q+7mCGB/Smkoupj5uqfyOX9P7Eq6ZliobfYeTuEY1P6AwszuIzelGLi4lNWzVxJjZBoRgtFOPfmqDYBK7suonEvnTwUsGnTgPRekU0/MrPuIeeS9wiwDdymUTlgrd9zYO64H5d8K6vCOcroL8BrRyW7yKwkjKKxJ667B3tMzl9SjxpYSeC4ZXk4aHizvOQuy9CnnxTDBYl0Nz8kl+uQqbWVWTVevRnLl+sBtp7j5qd42RVzEeHknuvB7z8PqXeC5FzRW3LXba93CVwOSCTyELhmqjqzRTaaBqRYrejeGNrmiONMCvgbS1BK8frlbh2nN2c6UITLzTBlUKa6s5fsyfHIQOHJ3wAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/19588ef9936c3cd565b75a60849c06fb/a331c/2024-01-28-cover.png" srcset="/static/19588ef9936c3cd565b75a60849c06fb/36ca5/2024-01-28-cover.png 200w, /static/19588ef9936c3cd565b75a60849c06fb/a3397/2024-01-28-cover.png 400w, /static/19588ef9936c3cd565b75a60849c06fb/a331c/2024-01-28-cover.png 800w, /static/19588ef9936c3cd565b75a60849c06fb/e4699/2024-01-28-cover.png 957w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>TypeScript is an intriguing language. Some say that its <a href="https://matt-rickard.com/typescript-type-system-hacks">type system, by itself, is Turing Complete</a>.</strong> Some take it to the extreme and even implement <a href="https://zackoverflow.dev/writing/flappy-bird-in-type-level-typescript/">Flappy Bird in TypeScript types</a>. No worries, we won’t take that far today, but I’ll show you a few tricks I learned recently. Of course, I’ll use the event-driven examples, but you can use those techniques for other needs.</p> <p>Let’s say that our system has two types of messages: commands and events. Commands represent the intention to run some business logic. Events are facts representing information about what has happened in our system. Essentially, events are the outcome of the command handling. (read a more nuanced take in <a href="/en/whats_the_difference_between_event_and_command/">What’s the difference between a command and an event?</a>).</p> <p>We could define command as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Command<span class="token operator">&lt;</span> CommandType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> CommandData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>CommandType<span class="token operator">></span><span class="token punctuation">;</span> data<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>CommandData<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>For instance, we can define the command representing the guest checkout as such:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">CheckOut</span> <span class="token operator">=</span> Command<span class="token operator">&lt;</span> <span class="token string">'CheckOut'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> now<span class="token operator">:</span> Date<span class="token punctuation">;</span> groupCheckoutId<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>and create the instance of it as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> checkout<span class="token operator">:</span> CheckOut <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CheckOut'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> now<span class="token operator">:</span> <span class="token function">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> groupCheckoutId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>So, the command definition tells that the command has type and data. That’s represented by <em>CommandType</em> and <em>CommandData</em> in the Command type definition. The weirdly looking <em>Record&#x3C;string, unknown></em> just tells that command data can be any object with properties. We’re also marking it as read-only, as it’s a <a href="https://martinfowler.com/eaaCatalog/dataTransferObject.html">Data Transfer Object</a> that carries data and, once created, should not be changed.</p> <p>Accordingly, we could define the Event type as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Event<span class="token operator">&lt;</span> EventType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> EventData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> EventType<span class="token punctuation">;</span> data<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>EventData<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>with example:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GuestCheckedOut</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'GuestCheckedOut'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> checkedOutAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> groupCheckoutId<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">const</span> checkedOut<span class="token operator">:</span> GuestCheckedOut <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GuestCheckedOut'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> checkedOutAt<span class="token operator">:</span> <span class="token function">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> groupCheckoutId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We could define the type for function making business logic decisions as :</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Decide<span class="token operator">&lt;</span> State <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token constant">C</span> <span class="token keyword">extends</span> Command<span class="token punctuation">,</span> <span class="token constant">E</span> <span class="token keyword">extends</span> Event<span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">(</span>command<span class="token operator">:</span> <span class="token constant">C</span><span class="token punctuation">,</span> state<span class="token operator">:</span> State<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token constant">E</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span></code></pre></div> <p>We take a command and a state, returning one or more events (read more in <a href="/en/type_script_node_Js_event_sourcing/">Straightforward Event Sourcing with TypeScript and NodeJS</a>). If we define all events and commands for Guest Stay, we can join those types with union.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GuestStayAccount</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'NotExisting'</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Opened'</span><span class="token punctuation">;</span> balance<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'CheckedOut'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GuestStayCommand</span> <span class="token operator">=</span> <span class="token operator">|</span> CheckIn <span class="token operator">|</span> RecordCharge <span class="token operator">|</span> RecordPayment <span class="token operator">|</span> CheckOut<span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GuestStayAccountEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> GuestCheckedIn <span class="token operator">|</span> ChargeRecorded <span class="token operator">|</span> PaymentRecorded <span class="token operator">|</span> GuestCheckedOut <span class="token operator">|</span> GuestCheckoutFailed<span class="token punctuation">;</span></code></pre></div> <p>This is a nice definition of the Guest Stay API. We can then encapsulate the business logic in:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> decide<span class="token operator">:</span> Decide<span class="token operator">&lt;</span> GuestStayAccount<span class="token punctuation">,</span> GuestStayCommand<span class="token punctuation">,</span> GuestStayAccountEvent <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> command <span class="token punctuation">}</span><span class="token operator">:</span> GuestStayCommand<span class="token punctuation">,</span> state<span class="token operator">:</span> GuestStayAccount<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> GuestStayAccountEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> now <span class="token punctuation">}</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'CheckIn'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'NotExisting'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'Guest is already checked-in!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GuestCheckedIn'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> checkedInAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'RecordCharge'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Opened'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'Guest account is already checked out!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ChargeRecorded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> amount<span class="token operator">:</span> command<span class="token punctuation">.</span>amount<span class="token punctuation">,</span> recordedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'RecordPayment'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Opened'</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'Guest account is already checked out!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'PaymentRecorded'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> amount<span class="token operator">:</span> command<span class="token punctuation">.</span>amount<span class="token punctuation">,</span> recordedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'CheckOut'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Opened'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> groupCheckoutId<span class="token operator">:</span> command<span class="token punctuation">.</span>groupCheckoutId<span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">'NotOpened'</span><span class="token punctuation">,</span> failedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> isSettled <span class="token operator">=</span> state<span class="token punctuation">.</span>balance <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>isSettled<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> groupCheckoutId<span class="token operator">:</span> command<span class="token punctuation">.</span>groupCheckoutId<span class="token punctuation">,</span> reason<span class="token operator">:</span> <span class="token string">'BalanceNotSettled'</span><span class="token punctuation">,</span> failedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GuestCheckedOut'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> groupCheckoutId<span class="token operator">:</span> command<span class="token punctuation">.</span>groupCheckoutId<span class="token punctuation">,</span> checkedOutAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> _notExistingCommandType<span class="token operator">:</span> <span class="token builtin">never</span> <span class="token operator">=</span> type<span class="token punctuation">;</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Unknown command type</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Sweet, we have the pattern for handling the business logic in Event Sourcing. Thanks to its predictable nature, it’s quite straightforward.</p> <p>Yet, things are getting more complex if we tackle more advanced business processes. Let’s try to describe the Group Checkout process that I modelled in the webinar:</p> <p><a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB8klEQVQoz4WST2sTURTF5wO59du49QO4kSBVQdwJbsRWwbiK1Z24UBQL8R8aCCQpaWtCkjYTJmmczLy/M4kxTX/yJk1sCsXF4XJ595173jnPG40ipFTEsVggEkgpkXJZJSIaoZRCa4PWOqtKacIwwhhLZfeAexv3+fD+G54xBgfrYC3GLvqLWBA5qFVVSjI/nbNdKPB08zG53AbecqtTIOIYLRXqrM/gLkuJ0RprE6xNscZikglCWWazGc/yeX62aty5ewsvikQ2HOmE6n6N+OAd0phse0bonj2eUu60qVWKNHc/U23u0f3+ClHf4c8cnufzFN/skLtxE8/5tpCvEUIgYx+pdearI7XOgu4hYbfDcdhnOOgy+DVgFDSJB0ecnM7ZerTF5tWXXLtyHc8RLTxSmCQl6vsMK2+JghZKGWpBiH1wm5PCE/RkitFnXidjtElIkoR2q037q0+5WMFzqWaESmVDUa/B8McLouYntJJ88UP62w8ZV0vIJM3m1sPRpGnKlN+EcfgvlJXCXpvj0mtCp7JTp+qH+JWPaHs+6XUoqbKg/K6P5+QvD4xNEIOAqFFCHJaRfp39fsxwr4QRMdpcTuqe3uv1nML1A+P8SSfZt9DJmNSFkk7OferLCYMgwHPJmoubM58WUKte/5ew0WjyFxc6K8nB1bJ+AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2023-09-22-webinar.png" srcset="/static/aeaac4b117eefd05e7a7793b42fef48d/36ca5/2023-09-22-webinar.png 200w, /static/aeaac4b117eefd05e7a7793b42fef48d/a3397/2023-09-22-webinar.png 400w, /static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2023-09-22-webinar.png 800w, /static/aeaac4b117eefd05e7a7793b42fef48d/e4699/2023-09-22-webinar.png 957w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p>In a nutshell, we want to speed up the people’s checkout by allowing the clerk to select multiple guest stays and run checkout as a batched, asynchronous process. Read also more in <a href="/en/event_driven_distributed_processes_by_example/#batch-operations">Event-driven distributed processes by example</a></p> <p><strong>Let’s try to reflect the process in code as described by Yves Reynhout in <a href="https://blog.bittacklr.be/the-workflow-pattern.html">The Workflow Pattern</a>.</strong></p> <p>The group checkout process can have the following inputs:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GroupCheckoutInput</span> <span class="token operator">=</span> <span class="token operator">|</span> InitiateGroupCheckout <span class="token operator">|</span> GuestCheckedOut <span class="token operator">|</span> GuestCheckoutFailed <span class="token operator">|</span> TimeoutGroupCheckout<span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">InitiateGroupCheckout</span> <span class="token operator">=</span> Command<span class="token operator">&lt;</span> <span class="token string">'InitiateGroupCheckout'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clerkId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> guestStayAccountIds<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> now<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">TimeoutGroupCheckout</span> <span class="token operator">=</span> Command<span class="token operator">&lt;</span> <span class="token string">'TimeoutGroupCheckout'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> startedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> timeOutAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GuestCheckedOut</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'GuestCheckedOut'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> checkedOutAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> groupCheckoutId<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GuestCheckoutFailed</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token string">'NotOpened'</span> <span class="token operator">|</span> <span class="token string">'BalanceNotSettled'</span><span class="token punctuation">;</span> failedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> groupCheckoutId<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>The clerk initiates the group checkout process after selecting a list of guests stays to checkout. Then, the next steps are triggered by guest checkout completion or failure. Once we get all events back, we can either complete the group checkout (if all guest checkouts succeeded) or fail it. We can also get the request to time out our workflow after the maximum processing time is reached.</p> <p>The state of our workflow can look like this:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GroupCheckout</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'NotExisting'</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Pending'</span><span class="token punctuation">;</span> guestStayAccountIds<span class="token operator">:</span> Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> GuestStayStatus<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Finished'</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> getInitialState <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">:</span> GroupCheckout <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'NotExisting'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">enum</span> GuestStayStatus <span class="token punctuation">{</span> Pending <span class="token operator">=</span> <span class="token string">'Pending'</span><span class="token punctuation">,</span> Completed <span class="token operator">=</span> <span class="token string">'Completed'</span><span class="token punctuation">,</span> Failed <span class="token operator">=</span> <span class="token string">'Failed'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s either not existing, pending or finished.</p> <p>The output of our process can be defined as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GroupCheckoutOutput</span> <span class="token operator">=</span> <span class="token operator">|</span> GroupCheckoutInitiated <span class="token operator">|</span> CheckOut <span class="token operator">|</span> GroupCheckoutCompleted <span class="token operator">|</span> GroupCheckoutFailed <span class="token operator">|</span> GroupCheckoutTimedOut<span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GroupCheckoutInitiated</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'GroupCheckoutInitiated'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clerkId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> guestStayAccountIds<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> initiatedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GroupCheckoutCompleted</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'GroupCheckoutCompleted'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> completedCheckouts<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> completedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GroupCheckoutFailed</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'GroupCheckoutFailed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> completedCheckouts<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> failedCheckouts<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> failedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GroupCheckoutTimedOut</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'GroupCheckoutTimedOut'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> incompleteCheckouts<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> completedCheckouts<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> failedCheckouts<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> timedOutAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">CheckOut</span> <span class="token operator">=</span> Command<span class="token operator">&lt;</span> <span class="token string">'CheckOut'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> now<span class="token operator">:</span> Date<span class="token punctuation">;</span> groupCheckoutId<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>As mentioned, once the clerk initiated the Group checkout, we spun out the set of <em>CheckOut</em> commands, and then we published the final events once the work was done.</p> <p><strong>Let’s now try to generalise the workflow definition using the TypeScript type system.</strong></p> <p>Workflow is defined by the set of inputs, outputs and the state. Inputs and outputs can be either commands or events. The heuristic is that inputs are mostly events, and outputs are mostly commands.</p> <p>Once the workflow is triggered by an input message, it needs to decide what to do next. The result is reflected by the list of output messages. Besides the payload (as <a href="https://blog.bittacklr.be/the-workflow-pattern.html">Yves explained in his article</a>) they can be enriched by the expected integration context (e.g. whether we’re sending commands, publishing events, scheduling delayed messages, etc.).</p> <p>It can look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">WorkflowOutput<span class="token operator">&lt;</span>TOutput <span class="token keyword">extends</span> Command <span class="token operator">|</span> Event<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Reply'</span><span class="token punctuation">;</span> message<span class="token operator">:</span> TOutput <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Send'</span><span class="token punctuation">;</span> message<span class="token operator">:</span> TOutput <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Publish'</span><span class="token punctuation">;</span> message<span class="token operator">:</span> TOutput <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Schedule'</span><span class="token punctuation">;</span> message<span class="token operator">:</span> TOutput<span class="token punctuation">;</span> when<span class="token operator">:</span> <span class="token punctuation">{</span> afterInMs<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> at<span class="token operator">:</span> Date <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Complete'</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Accept'</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Ignore'</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Error'</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>I extended the original set of semantics with Ignore, Accept and Error. Why? Read more in <a href="/en/no_it_can_never_happen/">No, it can never happen!</a>.</p> <p><strong>Now, what if we’d like to be explicit and say that we can send only commands and publish only events?</strong> We would like the TypeScript compiler to tell us if we try to use the event for command and publish for the event. Our current type definition doesn’t allow us to do that; why? Because of the <a href="/en/structural_typing_in_type_script/">Structural Typing in TypeScript</a>. Our command and event type definitions have exactly the same structure. Let’s get some flavour into it!</p> <p><strong>In TypeScript we have so called <em>Flavouring</em> and <em>Branding</em> techniques.</strong> <a href="https://brightinventions.pl/blog/branding-flavoring/">Wojciech Baczyński explained that in detail in his article</a>, but TLDR is that we can add a field that’ll differentiate our type. Branding requires providing this optional value; flavouring makes it optional. This can be reflected as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Brand<span class="token operator">&lt;</span><span class="token constant">K</span><span class="token punctuation">,</span> <span class="token constant">T</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">K</span> <span class="token operator">&amp;</span> <span class="token punctuation">{</span> <span class="token keyword">readonly</span> __brand<span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Flavour<span class="token operator">&lt;</span><span class="token constant">K</span><span class="token punctuation">,</span> <span class="token constant">T</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token constant">K</span> <span class="token operator">&amp;</span> <span class="token punctuation">{</span> <span class="token keyword">readonly</span> __brand<span class="token operator">?</span><span class="token operator">:</span> <span class="token constant">T</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Using this technique, we can update our Event and Command type definitions as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Event<span class="token operator">&lt;</span> EventType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> EventData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> Flavour<span class="token operator">&lt;</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> EventType<span class="token punctuation">;</span> data<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>EventData<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token string">'Event'</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Command<span class="token operator">&lt;</span> CommandType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> CommandData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> Flavour<span class="token operator">&lt;</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> CommandType<span class="token punctuation">;</span> data<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>CommandData<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token string">'Command'</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>This looks a bit weird, but we’re adding an additional field to tell the typescript compiler <em>“Hey, I’m Event!”</em> or <em>“Hey, I’m Command!”</em>.</p> <p>Now, having that, we can tell TypeScript that having a set of inputs, I’d like to take the subset of it. This subset should contain only event types:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">WorkflowEvent<span class="token operator">&lt;</span>Output <span class="token keyword">extends</span> Command <span class="token operator">|</span> Event<span class="token operator">></span></span> <span class="token operator">=</span> Extract<span class="token operator">&lt;</span> Output<span class="token punctuation">,</span> <span class="token punctuation">{</span> __brand<span class="token operator">?</span><span class="token operator">:</span> <span class="token string">'Event'</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>or just command types:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">WorkflowCommand<span class="token operator">&lt;</span>Output <span class="token keyword">extends</span> Command <span class="token operator">|</span> Event<span class="token operator">></span></span> <span class="token operator">=</span> Extract<span class="token operator">&lt;</span> Output<span class="token punctuation">,</span> <span class="token punctuation">{</span> __brand<span class="token operator">?</span><span class="token operator">:</span> <span class="token string">'Command'</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>We’re using the built-in <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#extracttype-union">Extract utility type</a>. It constructs a type by extracting all union members that are assignable to the union type provided as the type second parameter (so all with a specific brand: Event or Command).</p> <p>Thanks to that, this won’t compile:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> event<span class="token operator">:</span> WorkflowEvent<span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CheckOut'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> now<span class="token operator">:</span> <span class="token function">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> groupCheckoutId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>but this will:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> event<span class="token operator">:</span> WorkflowCommand<span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CheckOut'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> now<span class="token operator">:</span> <span class="token function">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> groupCheckoutId<span class="token operator">:</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p><strong>We can change our workflow output definition to be explicit and say that we can send only commands and publish only events.</strong></p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">WorkflowOutput<span class="token operator">&lt;</span>TOutput <span class="token keyword">extends</span> Command <span class="token operator">|</span> Event<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Reply'</span><span class="token punctuation">;</span> message<span class="token operator">:</span> TOutput <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Send'</span><span class="token punctuation">;</span> message<span class="token operator">:</span> WorkflowCommand<span class="token operator">&lt;</span>TOutput<span class="token operator">></span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Publish'</span><span class="token punctuation">;</span> message<span class="token operator">:</span> WorkflowEvent<span class="token operator">&lt;</span>TOutput<span class="token operator">></span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Schedule'</span><span class="token punctuation">;</span> message<span class="token operator">:</span> TOutput<span class="token punctuation">;</span> when<span class="token operator">:</span> <span class="token punctuation">{</span> afterInMs<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> at<span class="token operator">:</span> Date <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Complete'</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Accept'</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Ignore'</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> kind<span class="token operator">:</span> <span class="token string">'Error'</span><span class="token punctuation">;</span> reason<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>And the whole Worfklow definition as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Workflow<span class="token operator">&lt;</span> Input <span class="token keyword">extends</span> Event <span class="token operator">|</span> Command<span class="token punctuation">,</span> State<span class="token punctuation">,</span> Output <span class="token keyword">extends</span> Event <span class="token operator">|</span> Command<span class="token punctuation">,</span> <span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token function-variable function">decide</span><span class="token operator">:</span> <span class="token punctuation">(</span>command<span class="token operator">:</span> Input<span class="token punctuation">,</span> state<span class="token operator">:</span> State<span class="token punctuation">)</span> <span class="token operator">=></span> WorkflowOutput<span class="token operator">&lt;</span>Output<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token function-variable function">evolve</span><span class="token operator">:</span> <span class="token punctuation">(</span>currentState<span class="token operator">:</span> State<span class="token punctuation">,</span> event<span class="token operator">:</span> WorkflowEvent<span class="token operator">&lt;</span>Output<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token function-variable function">getInitialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Besides what we have discussed so far, I added the requirement to provide the initial state of the workflow through the <em>getInitialState</em> method.</p> <p>I also introduced the <em>evolve</em> function. We want to store all input and output events to have the full tracing. I’ll also use them to build the state of the workflow from them. Read more in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a>. That’s optional, but you should already know, that I like Event Sourcing. It can look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> evolve <span class="token operator">=</span> <span class="token punctuation">(</span> state<span class="token operator">:</span> GroupCheckout<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> event<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token operator">:</span> WorkflowEvent<span class="token operator">&lt;</span>GroupCheckoutInput <span class="token operator">|</span> GroupCheckoutOutput<span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> GroupCheckout <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'GroupCheckoutInitiated'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'NotExisting'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Pending'</span><span class="token punctuation">,</span> guestStayAccountIds<span class="token operator">:</span> event<span class="token punctuation">.</span>guestStayAccountIds<span class="token punctuation">.</span><span class="token function">reduce</span><span class="token punctuation">(</span> <span class="token punctuation">(</span>map<span class="token punctuation">,</span> id<span class="token punctuation">)</span> <span class="token operator">=></span> map<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> GuestStayStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token generic-function"><span class="token function">Map</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> GuestStayStatus<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckedOut'</span><span class="token operator">:</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>state<span class="token punctuation">,</span> guestStayAccountIds<span class="token operator">:</span> state<span class="token punctuation">.</span>guestStayAccountIds<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span> event<span class="token punctuation">.</span>guestStayAccountId<span class="token punctuation">,</span> type <span class="token operator">===</span> <span class="token string">'GuestCheckedOut'</span> <span class="token operator">?</span> GuestStayStatus<span class="token punctuation">.</span>Completed <span class="token operator">:</span> GuestStayStatus<span class="token punctuation">.</span>Failed<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'GroupCheckoutCompleted'</span><span class="token operator">:</span> <span class="token keyword">case</span> <span class="token string">'GroupCheckoutFailed'</span><span class="token operator">:</span> <span class="token keyword">case</span> <span class="token string">'GroupCheckoutTimedOut'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Finished'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> _notExistingEventType<span class="token operator">:</span> <span class="token builtin">never</span> <span class="token operator">=</span> type<span class="token punctuation">;</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’re having fun with the TypeScript system, saying that to evolve the state, we’ll just use events from workflow inputs and outputs. We can do that by simply calling:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> events<span class="token operator">:</span> <span class="token punctuation">(</span>GroupCheckoutInput <span class="token operator">|</span> GroupCheckoutOutput<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">=</span> eventStore<span class="token punctuation">.</span><span class="token function">readEvents</span><span class="token punctuation">(</span>workflowId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> initialState <span class="token operator">=</span> <span class="token function">getInitialState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> state <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">reduce</span><span class="token generic class-name"><span class="token operator">&lt;</span>GroupCheckout<span class="token operator">></span></span></span><span class="token punctuation">(</span>evolve<span class="token punctuation">,</span> initialState<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Such state can be used in the decision making as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> decide <span class="token operator">=</span> <span class="token punctuation">(</span> input<span class="token operator">:</span> GroupCheckoutInput<span class="token punctuation">,</span> state<span class="token operator">:</span> GroupCheckout<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WorkflowOutput<span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> type <span class="token punctuation">}</span> <span class="token operator">=</span> input<span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'InitiateGroupCheckout'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">initiate</span><span class="token punctuation">(</span>input<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckedOut'</span><span class="token operator">:</span> <span class="token keyword">case</span> <span class="token string">'GuestCheckoutFailed'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token function">tryComplete</span><span class="token punctuation">(</span>input<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'TimeoutGroupCheckout'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">timeOut</span><span class="token punctuation">(</span>input<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> _notExistingEventType<span class="token operator">:</span> <span class="token builtin">never</span> <span class="token operator">=</span> type<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token string">'UnknownInputType'</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Togethere with handling methods:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> initiate <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> command <span class="token punctuation">}</span><span class="token operator">:</span> InitiateGroupCheckout<span class="token punctuation">,</span> state<span class="token operator">:</span> GroupCheckout<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WorkflowOutput<span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'NotExisting'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token function">ignore</span><span class="token punctuation">(</span>IgnoredReason<span class="token punctuation">.</span>GroupCheckoutAlreadyInitiated<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> checkoutGuestStays <span class="token operator">=</span> command<span class="token punctuation">.</span>guestStayAccountIds<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token generic-function"><span class="token function">send</span><span class="token generic class-name"><span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CheckOut'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token operator">:</span> id<span class="token punctuation">,</span> now<span class="token operator">:</span> command<span class="token punctuation">.</span>now<span class="token punctuation">,</span> groupCheckoutId<span class="token operator">:</span> command<span class="token punctuation">.</span>groupCheckoutId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token operator">...</span>checkoutGuestStays<span class="token punctuation">,</span> <span class="token generic-function"><span class="token function">publish</span><span class="token generic class-name"><span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GroupCheckoutInitiated'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token operator">:</span> command<span class="token punctuation">.</span>groupCheckoutId<span class="token punctuation">,</span> guestStayAccountIds<span class="token operator">:</span> command<span class="token punctuation">.</span>guestStayAccountIds<span class="token punctuation">,</span> initiatedAt<span class="token operator">:</span> command<span class="token punctuation">.</span>now<span class="token punctuation">,</span> clerkId<span class="token operator">:</span> command<span class="token punctuation">.</span>clerkId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> tryComplete <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> GuestCheckedOut <span class="token operator">|</span> GuestCheckoutFailed<span class="token punctuation">,</span> state<span class="token operator">:</span> GroupCheckout<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WorkflowOutput<span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>event<span class="token punctuation">.</span>groupCheckoutId<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token function">ignore</span><span class="token punctuation">(</span>IgnoredReason<span class="token punctuation">.</span>GuestCheckoutWasNotPartOfGroupCheckout<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'NotExisting'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token function">ignore</span><span class="token punctuation">(</span>IgnoredReason<span class="token punctuation">.</span>GroupCheckoutDoesNotExist<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'Finished'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token function">ignore</span><span class="token punctuation">(</span>IgnoredReason<span class="token punctuation">.</span>GuestCheckoutAlreadyFinished<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> guestStayAccountId<span class="token punctuation">,</span> groupCheckoutId <span class="token punctuation">}</span> <span class="token operator">=</span> event<span class="token punctuation">;</span> <span class="token keyword">const</span> guestCheckoutStatus <span class="token operator">=</span> state<span class="token punctuation">.</span>guestStayAccountIds<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>guestStayAccountId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">isAlreadyClosed</span><span class="token punctuation">(</span>guestCheckoutStatus<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token function">ignore</span><span class="token punctuation">(</span>IgnoredReason<span class="token punctuation">.</span>GuestCheckoutAlreadyFinished<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> guestStayAccountIds <span class="token operator">=</span> state<span class="token punctuation">.</span>guestStayAccountIds<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span> guestStayAccountId<span class="token punctuation">,</span> type <span class="token operator">===</span> <span class="token string">'GuestCheckedOut'</span> <span class="token operator">?</span> GuestStayStatus<span class="token punctuation">.</span>Completed <span class="token operator">:</span> GuestStayStatus<span class="token punctuation">.</span>Failed<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> now <span class="token operator">=</span> type <span class="token operator">===</span> <span class="token string">'GuestCheckedOut'</span> <span class="token operator">?</span> event<span class="token punctuation">.</span>checkedOutAt <span class="token operator">:</span> event<span class="token punctuation">.</span>failedAt<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">areAnyOngoingCheckouts</span><span class="token punctuation">(</span>guestStayAccountIds<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token punctuation">[</span><span class="token function">accept</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token function">publish</span><span class="token punctuation">(</span><span class="token function">finished</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> state<span class="token punctuation">.</span>guestStayAccountIds<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">complete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> timeOut <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> event <span class="token punctuation">}</span><span class="token operator">:</span> TimeoutGroupCheckout<span class="token punctuation">,</span> state<span class="token operator">:</span> GroupCheckout<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> WorkflowOutput<span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'NotExisting'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token function">ignore</span><span class="token punctuation">(</span>IgnoredReason<span class="token punctuation">.</span>GroupCheckoutDoesNotExist<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>status <span class="token operator">===</span> <span class="token string">'Finished'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token function">ignore</span><span class="token punctuation">(</span>IgnoredReason<span class="token punctuation">.</span>GroupCheckoutAlreadyFinished<span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token punctuation">,</span> timeOutAt <span class="token punctuation">}</span> <span class="token operator">=</span> event<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> guestStayAccountIds <span class="token punctuation">}</span> <span class="token operator">=</span> state<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">[</span> <span class="token generic-function"><span class="token function">publish</span><span class="token generic class-name"><span class="token operator">&lt;</span>GroupCheckoutOutput<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GroupCheckoutTimedOut'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token punctuation">,</span> incompleteCheckouts<span class="token operator">:</span> <span class="token function">checkoutsWith</span><span class="token punctuation">(</span> guestStayAccountIds<span class="token punctuation">,</span> GuestStayStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> completedCheckouts<span class="token operator">:</span> <span class="token function">checkoutsWith</span><span class="token punctuation">(</span> guestStayAccountIds<span class="token punctuation">,</span> GuestStayStatus<span class="token punctuation">.</span>Completed<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> failedCheckouts<span class="token operator">:</span> <span class="token function">checkoutsWith</span><span class="token punctuation">(</span> guestStayAccountIds<span class="token punctuation">,</span> GuestStayStatus<span class="token punctuation">.</span>Failed<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> timedOutAt<span class="token operator">:</span> timeOutAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">complete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>and helpers to make processing more explicit:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">isAlreadyClosed</span> <span class="token operator">=</span> <span class="token punctuation">(</span>status<span class="token operator">:</span> GuestStayStatus <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token operator">=></span> status <span class="token operator">===</span> GuestStayStatus<span class="token punctuation">.</span>Completed <span class="token operator">||</span> status <span class="token operator">===</span> GuestStayStatus<span class="token punctuation">.</span>Failed<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">areAnyOngoingCheckouts</span> <span class="token operator">=</span> <span class="token punctuation">(</span> guestStayAccounts<span class="token operator">:</span> Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> GuestStayStatus<span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token operator">=></span> guestStayAccounts<span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">!</span><span class="token function">isAlreadyClosed</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">areAllCompleted</span> <span class="token operator">=</span> <span class="token punctuation">(</span>guestStayAccounts<span class="token operator">:</span> Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> GuestStayStatus<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> guestStayAccounts<span class="token punctuation">.</span><span class="token function">some</span><span class="token punctuation">(</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span> <span class="token operator">=></span> status <span class="token operator">===</span> GuestStayStatus<span class="token punctuation">.</span>Completed<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> checkoutsWith <span class="token operator">=</span> <span class="token punctuation">(</span> guestStayAccounts<span class="token operator">:</span> Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> GuestStayStatus<span class="token operator">></span><span class="token punctuation">,</span> status<span class="token operator">:</span> GuestStayStatus<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token builtin">Array</span><span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>guestStayAccounts<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span>s<span class="token punctuation">)</span> <span class="token operator">=></span> s <span class="token operator">===</span> status<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> finished <span class="token operator">=</span> <span class="token punctuation">(</span> groupCheckoutId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> guestStayAccounts<span class="token operator">:</span> Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> GuestStayStatus<span class="token operator">></span><span class="token punctuation">,</span> now<span class="token operator">:</span> Date<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token operator">:</span> GroupCheckoutCompleted <span class="token operator">|</span> GroupCheckoutFailed <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">areAllCompleted</span><span class="token punctuation">(</span>guestStayAccounts<span class="token punctuation">)</span> <span class="token operator">?</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GroupCheckoutCompleted'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token punctuation">,</span> completedCheckouts<span class="token operator">:</span> <span class="token builtin">Array</span><span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span>guestStayAccounts<span class="token punctuation">.</span><span class="token function">values</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> completedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'GroupCheckoutFailed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> groupCheckoutId<span class="token punctuation">,</span> completedCheckouts<span class="token operator">:</span> <span class="token function">checkoutsWith</span><span class="token punctuation">(</span> guestStayAccounts<span class="token punctuation">,</span> GuestStayStatus<span class="token punctuation">.</span>Completed<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> failedCheckouts<span class="token operator">:</span> <span class="token function">checkoutsWith</span><span class="token punctuation">(</span> guestStayAccounts<span class="token punctuation">,</span> GuestStayStatus<span class="token punctuation">.</span>Failed<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> failedAt<span class="token operator">:</span> now<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">enum</span> IgnoredReason <span class="token punctuation">{</span> GroupCheckoutAlreadyInitiated <span class="token operator">=</span> <span class="token string">'GroupCheckoutAlreadyInitiated'</span><span class="token punctuation">,</span> GuestCheckoutWasNotPartOfGroupCheckout <span class="token operator">=</span> <span class="token string">'GuestCheckoutWasNotPartOfGroupCheckout'</span><span class="token punctuation">,</span> GuestCheckoutAlreadyFinished <span class="token operator">=</span> <span class="token string">'GuestCheckoutAlreadyFinished'</span><span class="token punctuation">,</span> GroupCheckoutAlreadyFinished <span class="token operator">=</span> <span class="token string">'GroupCheckoutAlreadyFinished'</span><span class="token punctuation">,</span> GroupCheckoutDoesNotExist <span class="token operator">=</span> <span class="token string">'GroupCheckoutDoesNotExist'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ErrorReason</span> <span class="token operator">=</span> <span class="token string">'UnknownInputType'</span><span class="token punctuation">;</span></code></pre></div> <p>Thanks to the <em>WorkflowOutput</em> definition, we won’t be able to produce wrong values.</p> <p>The final definition of our workflow will look as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> GroupCheckoutWorkflow<span class="token operator">:</span> Workflow<span class="token operator">&lt;</span> GroupCheckoutInput<span class="token punctuation">,</span> GroupCheckout<span class="token punctuation">,</span> GroupCheckoutOutput <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> decide<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> getInitialState<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>And hey, we modelled the whole workflow, keeping it expressive and type-safe. We also had some fun with the TypeScript type system and reached the end of this article!</p> <p>I encourage you to play more with TypeScript or similar languages; with advanced types of systems, you can safely model both domain and infrastructure code! Speaking about the code, see the full sample <a href="https://github.com/oskardudycz/EventSourcing.NodeJS/tree/2d417bd2e650c16b4af68b6a752bd684ce0a8fc0/samples/hotelManagement/src/workflows">in my repository</a>.</p> <p>Also, stay tuned. In one of the following articles, I’ll show you how to plug that into the actual infrastructure!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Stream ids, event types prefixes and other event data you might not want to slice off]]>https://event-driven.io/en/on_putting_stream_id_in_event_data/https://event-driven.io/en/on_putting_stream_id_in_event_data/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/277c9e214c497e79574a0cadbf4a3eff/d2429/2024-01-21-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABQ0lEQVQoz62TTUsDMRCGM5NkUxa11gVbW1yhoihUxRYsbZctxYMoWgoiVTyoB6lU6EEvgh7Egx8HwZvgB3jxL/j3XmkQD6K4FQ9hSObNQ96ZiRBCIMqiiDrxu4DAUtn4L0BmCWKFWjGPyfHs34HMCkoqkNRQxLjvbGJzKbQ5ydQbUFqLjFw2gyk/Y8/K01l0NhpIDA1b+0QUDUjE1mY6kcB+GOLu6hqLpRIq/hhuDw9wubOHIF/80FK0FzpaY61QwO5CCctBDU+PL3g7v8Hr0TEe2i2cNLehTV90oGQF47hQMRfpuEF1zsdFewvPnSZaKyHO6usIZuZtWb6B/lRchtfvIsiNIOfH4Q0YTIwmUS/P4rS+ikalCiEUJMve59B2XRk4xrX71KCHlJeEYKf3Oez+ECYBrTVMLAal1KeDbgO/3nkHzEv+HwWmtUQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/277c9e214c497e79574a0cadbf4a3eff/a331c/2024-01-21-cover.png" srcset="/static/277c9e214c497e79574a0cadbf4a3eff/36ca5/2024-01-21-cover.png 200w, /static/277c9e214c497e79574a0cadbf4a3eff/a3397/2024-01-21-cover.png 400w, /static/277c9e214c497e79574a0cadbf4a3eff/a331c/2024-01-21-cover.png 800w, /static/277c9e214c497e79574a0cadbf4a3eff/d2429/2024-01-21-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>You’re reading much more code than you’re writing. Readability is a highly subjective term. That’s probably why some call what we’re doing <em>craft</em> or even <em>art</em>. <a href="/en/one_or_more_event_that_is_the_question/">Last week, we discussed if optimising for code size will help in that</a> (TLDR, typically, it won’t). Let’s continue those considerations using the event-based model for our process. Again, we’ll use a shopping cart as an example. It is a bit worn-out example, but it’ll do for today.</p> <p>Let’s say we have the following description of our process (written in TypeScript):</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartCancelled'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> cancelledAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>It tells the story of the shopping cart. The customer opens a shopping cart and then adds some products. Optionally, they can remove some products and eventually confirm or cancel if they realise they are not in the mood to spend money.</p> <p>I’m using TypeScript union types to reflect that we may record either of those facts (event types) for a shopping cart. Read more in <a href="/en/type_script_node_Js_event_sourcing/">Straightforward Event Sourcing with TypeScript and NodeJS</a>.</p> <p>Looking at this code, some may say:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3be865a86421b9239afbac967554fdab/00012/2024-01-21-buzz.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMBAgT/xAAVAQEBAAAAAAAAAAAAAAAAAAACA//aAAwDAQACEAMQAAABVaHCmcWFf//EABkQAQACAwAAAAAAAAAAAAAAAAEAAhAREv/aAAgBAQABBQIYwlQ1xXH/xAAWEQEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQMBAT8BrH//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAgEBPwFX/8QAGBAAAgMAAAAAAAAAAAAAAAAAABABITH/2gAIAQEABj8CLl4v/8QAGxAAAgMAAwAAAAAAAAAAAAAAASEAETFBUdH/2gAIAQEAAT8hobXU3XDbhKEMBsOlbPkIZn//2gAMAwEAAgADAAAAEMfP/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARMf/aAAgBAwEBPxAHIyX/xAAXEQEBAQEAAAAAAAAAAAAAAAABABEh/9oACAECAQE/EBXs6b//xAAaEAACAwEBAAAAAAAAAAAAAAABEQAhMWFB/9oACAEBAAE/EBN5HbOwyASCIWxxLHCtrNjkBq8sriCAgKZn/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="buzz showing shopping cart ids everywhere" title="buzz showing shopping cart ids everywhere" src="/static/3be865a86421b9239afbac967554fdab/c60e9/2024-01-21-buzz.jpg" srcset="/static/3be865a86421b9239afbac967554fdab/37402/2024-01-21-buzz.jpg 200w, /static/3be865a86421b9239afbac967554fdab/4cda9/2024-01-21-buzz.jpg 400w, /static/3be865a86421b9239afbac967554fdab/c60e9/2024-01-21-buzz.jpg 800w, /static/3be865a86421b9239afbac967554fdab/00012/2024-01-21-buzz.jpg 888w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>And some may be right, as we have it everywhere. In the event type union, in the event names, we also have the shopping cart id in each event’s data. If we <a href="https://github.com/oskardudycz/EventSourcing.NodeJS/blob/main/samples/decider/src/shoppingCarts/shoppingCart.ts">keep that event together with the shopping cart entity definition, e.g. in the <em>shoppingCart.ts</em> file</a>, the context could be clear enough not to add it everywhere.</p> <p>After dropping it, our model would look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Opened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemoved'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Confirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Cancelled'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> cancelledAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Now, if we look at how event stores are built (using <a href="https://martendb.io/">Marten</a> as an example), we may notice that each event will have its own metadata. Metadata will contain stream id. Then why repeat that in all events?</p> <p>If we removed it then our model would look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Opened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemoved'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Confirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> confirmedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Cancelled'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> cancelledAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>One could even go to the extreme and remove the timestamps from event data, as we could also have it in the event metadata. That’d end up as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Opened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemoved'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Confirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Cancelled'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Look Ma, no repetition!</strong> Isn’t that great?</p> <p>Let’s start with the last transformation. Dates are usually quite important. If you buy a car, you’ll have multiple dates involved:</p> <ul> <li>the date you signed the sales agreement,</li> <li>the date you’ve notified the government about the purchase,</li> <li>the date you got the registration certificate.</li> </ul> <p>Each of them may be a different one. If you don’t provide them explicitly or, worse, use a single date for them, you’ll get your process wrong.</p> <p><strong>Timestamps generated by event stores represent the system time when events were physically stored.</strong> Such information probably won’t be the same as the business date of the fact you’re registering. They may be close, but being close can be quite far away from being correct.</p> <p>Also, event stores don’t allow you to provide those dates. If you’ll need to migrate your data at some point, you’ll be doomed. Don’t you plan migration? They happen more often than you expect. Sometimes, upgrading to the next database version may require migration if you aren’t moving them continuously. Of course, you can do the transformation on the fly, use timestamps from the previous installation and enrich event data. Still, migrations are already not a great experience, so why add more unpleasant steps?</p> <p><strong>OK, then what about not having the record ID in events?</strong> I think that events are a form of documentation of our process. We can read the code and understand the flow. If we add a product item, we add it to the specific shopping cart. That’s business information. If we remove it from the event data, then we say this information is not essential.</p> <p>Still, this approach is quite popular and recommended by some people. Why?</p> <ul> <li>for some people, keeping that information in event data adds additional noise to understanding the flow. You may assume that your stream will have its id and that all events will share them.</li> <li>we’re duplicating data and increasing the event’s payload redundantly.</li> </ul> <p>I get those comments, and I’m not fully against them. I think they’re fair, but my preference is a bit different. I think keeping them is more explicit and better tells us the story of what has happened.</p> <blockquote> <p>What about shortening and just removing the shopping cart prefix?</p> </blockquote> <p>Let’s have a look:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemoved'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Such structure leaves more questions than answers. What does <em>“id”</em> even mean? Is it a shopping cart id or product id? Or maybe the user id? Nah, don’t do that.</p> <p><strong>Just like we’re designing our code with the reader in mind, we should also design our events.</strong> We need to remember that domain logic is just one of the usages. We also record those events to <a href="/en/projections_and_read_models_in_event_driven_architecture/">build read models</a> and <a href="/en/internal_external_events/">integrate them with other modules</a>. We may fully understand the context and be able to cut corners and reduce the potential noise. But, what’s noisy for us may be essential to get understanding for others.</p> <p>Have a look at the following event:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAdded'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>If you see such an event for the first time, would you be 100% sure that it’s coming from the shopping cart? Even if you know, wouldn’t you be tempted to double-check that? Wouldn’t adding <em>ShoppingCart</em> prefix helped? How much would it hurt you?</p> <p>Not persuaded enough? Then what would you tell me if you saw the following event for the first time?</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'Confirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token builtin">never</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Shortened event-type names make <a href="/en/how_to_map_event_type_by_convention/">convention-based event type mapping</a> harder. Some will say that’s great, as you should use <a href="/en/explicit_events_serialisation_in_event_sourcing/">explicit events serialisation</a>.</p> <p><strong>To be clear, I don’t say that not putting id into event data is a big mistake.</strong> We can make that one of the project conventions. But, well, don’t you already have a lot of conventions to remember?</p> <p>I hope this post will make it easier to understand what you’re signing for when choosing your preferred noise-reduction filter. My personal preference is to keep events self-explanatory. I’m a simple man; trying to be too clever usually ends badly for me.</p> <p><strong>Please also don’t take those considerations as general advice to use redundancy for the win.</strong> I explained that <a href="/en/i_will_just_add_one_more_field/"><em>“I’ll just add one more field”</em> attitude is an anti-pattern</a>.</p> <p>Getting the simple model is not easy. We must remember that we’re modelling to solve the business process most efficiently. And I believe that keeping our code close to that process and explicit is one of the essential tools to achieve that.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Check also <a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a></p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Should you record multiple events from business logic?]]>https://event-driven.io/en/one_or_more_event_that_is_the_question/https://event-driven.io/en/one_or_more_event_that_is_the_question/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d7b0f9d0b0dd689cbe371fd616629a7c/d2429/2024-01-14-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AKOIVqeKVpl/UaSIVbaaY7WaYpRwUZBtVIhuR5d7UYlsRI5uQph2VI5zS6SMXrWaZbOYY7aaZrSZZbmRVwCfhFOXfE6Zfk6ihlOqkFuhg1OMYEqyhG90VjiagFWGbEODYkTHmIeQaUuaglOtkV6vk2CxlWKylmK5kVUAkXhOmHxLk3hJn4RSpIdTeVo0aT0oh1Y/Ti4alnVLhmlAZ0Utr3pjek84i3BGsZZirZBdq49drJBdsotSAJV6T5B0SI9zRp2BTZqAVEYuHiIMBjskGjwlG52FZYttR0swIFs6LSgMBHBMK6uPW6eMWqeLWaWKXKeMZgCKc02Ga0GKbkGZe0SXinhveIWZs7+nw8+Fl6igqrakrrisxs631NV+j5p4dneqjl2kh1Seg1ahiV+nkXQAg25Ng2pDf2U7l3tJgmZIeHV6r9f3sNj5kqrDlHpugnp6qs7qyPT/rtPth4OGoIVbnIBOmoFXoYpmmoZxAIVvUHtkP35kOo90RX5aM2tVTZmjsqy+1X2HlZpuW31bToWbtLrZ+o+mwXpeTZd3R5R7T5uBWpaAZZqHcgB/aU52YD54XzmCaDyMZkebl6Gdq7y4x9ilwduyj4mfh4WhuMyytsOcprOHa2KPckuNd02QelqZhGmZhnEAfWhSeGJDcFk3fGQ6h2FFmJWijKG2qbvRlbHMm39/oZ+roMLimam+mrTOnIeGjW1IhXBNk31ekHtkj3xtAH1pVHBaQm5YOHZiPXJNMZGHjoaYqqKxw4eZrJR0cZGZqJaxy5mktpGqwZB8eoFkRoVxT4dyWol1YpB8agB4ZFBwW0RrUTF5Vy9vRCOSg4eEmKyWqr6PlqSFW1FndIOFmrCFjp19kKSPdG2LYzqAZ0OFcVqNeGGGcmTDHEXH4QYaFwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/d7b0f9d0b0dd689cbe371fd616629a7c/a331c/2024-01-14-cover.png" srcset="/static/d7b0f9d0b0dd689cbe371fd616629a7c/36ca5/2024-01-14-cover.png 200w, /static/d7b0f9d0b0dd689cbe371fd616629a7c/a3397/2024-01-14-cover.png 400w, /static/d7b0f9d0b0dd689cbe371fd616629a7c/a331c/2024-01-14-cover.png 800w, /static/d7b0f9d0b0dd689cbe371fd616629a7c/d2429/2024-01-14-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Asking people for feedback is an intriguing story.</strong> I like to get constructive criticism for my work, as that allows me to learn something new. When I prepare some new samples or have some new ideas, I like to crunch them with the people. Getting feedback is not easy, and good feedback is even more challenging.</p> <p>Preparing samples and learning materials takes work. You want to make them precise. Showing too much will blur the main idea, and introducing too many pieces will confuse people and make it harder for them to grasp what you want to convey. If you show too little, you’ll hear <em>“That’s nice but show me real code.”</em>. And that’s fair, as the example needs to be relatable to be transferable into real projects.</p> <p><strong>The solution is to be precise and provide a complex case around the stuff you want to explain but simplify the <em>side story</em>.</strong> Here’s the danger, of course, as one may ignore the essence and take oversimplification as best practice. So you better be explicit. But then, if you’re explicit too much, you start to rumble, like I do right now. So let’s try to pull into shore!</p> <p>For some reason, I’m getting feedback on my Event Sourcing sample: <em>“Why are you not returning multiple events from business logic?”</em>. And that question always catches me off guard. It’s surprising that that’s so important for so many people. Why would I use multiple events? Let’s discuss that, then!</p> <p><strong>It may be counterintuitive, but returning multiple granular events may be less precise than returning one bigger one.</strong> Let’s say that our stream looks like that:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">1. RoomReserved 2. GuestsInformationUpdated 3. GuestCheckedIn</code></pre></div> <p>When I looked at such an event stream, the story would look to me as follows: the user reserved the room, then provided the guest’s details or changed the number of people, and eventually checked in. Of course, I need to look at the event data to understand the details, but I expect the overall story to be seen from my events.</p> <p>Tho! What if someone optimised the event model on the code size and reusability? What if someone thought:</p> <blockquote> <p>“Hmm, I’ll have to support guest information updates; I’ll have the <em>GuestsInformationUpdated</em> event eventually. I also heard that events should be small and precise. Then maybe when I get a reservation, I’ll append two events <em>RoomReserved</em> and <em>GuestsInformationUpdated</em>.</p> </blockquote> <p>Thanks to that, this person could reuse the events. Potentially optimise the message size, which could make it a bit faster when this event is propagated. You could also keep less code, as in the projection you could do:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">ReservationProjection</span> <span class="token punctuation">{</span> <span class="token return-type class-name">Reservation</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">RoomReserved</span> roomReserved<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Reservation</span><span class="token punctuation">(</span>roomReserved<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> roomServerd<span class="token punctuation">.</span>Number<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token return-type class-name">Reservation</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">Reservation</span> reservation<span class="token punctuation">,</span> <span class="token class-name">GuestsInformationUpdated</span> guestInfoUpdated<span class="token punctuation">)</span> <span class="token operator">=></span> reservation <span class="token keyword">with</span> <span class="token punctuation">{</span> GuestsCount <span class="token operator">=</span> guestInfoUpdated<span class="token punctuation">.</span>GuestsCount<span class="token punctuation">,</span> MainGuest <span class="token operator">=</span> guestInfoUpdated<span class="token punctuation">.</span>MainGuest<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Instead of:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">ReservationProjection</span> <span class="token punctuation">{</span> <span class="token return-type class-name">Reservation</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">RoomReserved</span> roomReserved<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Reservation</span> <span class="token punctuation">(</span> roomReserved<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> roomServerd<span class="token punctuation">.</span>Number<span class="token punctuation">,</span> roomReserved<span class="token punctuation">.</span>GuestsInfo<span class="token punctuation">.</span>Count<span class="token punctuation">,</span> roomReserved<span class="token punctuation">.</span>GuestsInfo<span class="token punctuation">.</span>MainGuest<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token return-type class-name">Reservation</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">Reservation</span> reservation<span class="token punctuation">,</span> <span class="token class-name">GuestsInformationUpdated</span> guestInfoUpdated<span class="token punctuation">)</span> <span class="token operator">=></span> reservation <span class="token keyword">with</span> <span class="token punctuation">{</span> GuestsCount <span class="token operator">=</span> guestInfoUpdated<span class="token punctuation">.</span>GuestsCount<span class="token punctuation">,</span> MainGuest <span class="token operator">=</span> guestInfoUpdated<span class="token punctuation">.</span>MainGuest<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Same for other projections and handlers. Yet, is it such a big optimisation? Especially since now, for each handler, we need to handle those two events instead of a single one when we’re just interested in starting the reservation process. So, what are we getting out of it?</p> <p><strong>We’re definitely losing clarity on what has happened from the business process.</strong> Of course, we could use <a href="/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/">telemetry data like correlation id</a> and see that by checking metadata:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">1. RoomReserved (CorrelationId: '19n8') 2. GuestsInformationUpdated (CorrelationId: '19n8') 3. GuestCheckedIn (CorrelationId: '9f873')</code></pre></div> <p>But that only gives us a clue that they were stored in the same process. And what if we also did other optimisation and ran <a href="/en/simple_transactional_command_orchestration/">multiple commands in the same transaction</a>? Then, this correlation ID may mean that they just happen to be stored in the same request, and we need to do more detective work. Having multiple events as a result of a single request can also be a smell of wrong process boundaries. Maybe we should break it into multiple steps. See more in <a href="/en/saga_process_manager_distributed_transactions/">Saga and Process Manager - distributed processes in practice</a>.</p> <p><a href="/en/never_lose_data_with_event_sourcing/">One of the biggest advantages of using Event Sourcing is keeping the business context</a>. We’re trading it here for potentially less code. I already said that’s disputable, as that can only be true if we have read models representing 1:1 our write model. This may happen, but it’s again different from the premises of Event Sourcing.</p> <p><strong>What about adding a common interface or base class?</strong> Same story. Things tend to look similar at the beginning, but as our system evolves, they become more and more distinct. Adding something to the base class is too easy. You start adding data that some of the derived classes don’t need. Your code starts to drive your event model.</p> <p><strong>Reusing code in the events model just adds more coupling and increases cognitive load.</strong> Events are facts; business logic stores them as information about what has happened. It should not assume too many subscribers or projection needs. We can do tradeoffs and try to guess, but the more we do it, the less precise our model becomes.</p> <p>Having interfaces and base classes also destroys another benefit of the event model: documentation as code. We need to continuously switch between events and base classes/interfaces. That’s not a great experience.</p> <p><strong>Don’t optimise for code size; optimise for the right model to fulfil your business process well.</strong> Code is a liability, a tool to achieve that, nothing more than that. A little bit of healthy copy/paste won’t harm you. It’ll make your code cohesive and less coupled.</p> <p>Also, if your process has multiple inputs, for instance, UI, import from an external system, or another background process, keeping different event types is okay. You could have them as such:</p> <ul> <li><em>RoomReserved</em></li> <li><em>RoomReservedTentatively</em></li> <li><em>RoomReservedFromBookingComImported</em></li> <li><em>GroupRoomReservationMade</em></li> </ul> <p>As long as they’re meaningful for business that’s perfectly fine. Again, the closer your event model is to business, the better. Of course, I’m not encouraging you to create numerous event types and be too creative. I encourage you to be close to your business.</p> <p><strong>The most important thing is to see a clear business intention and have all the information about what has happened.</strong> Whether to keep separate events or group them, providing different events for potentially the same one is highly dependent on the specific business use case.</p> <p><strong>I’d add two events if there were separate parts of the processes.</strong> Let’s take the guest’s group checkout we modelled in <a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed">webinar about implementing distributed processes</a></p> <p><a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB8klEQVQoz4WST2sTURTF5wO59du49QO4kSBVQdwJbsRWwbiK1Z24UBQL8R8aCCQpaWtCkjYTJmmczLy/M4kxTX/yJk1sCsXF4XJ595173jnPG40ipFTEsVggEkgpkXJZJSIaoZRCa4PWOqtKacIwwhhLZfeAexv3+fD+G54xBgfrYC3GLvqLWBA5qFVVSjI/nbNdKPB08zG53AbecqtTIOIYLRXqrM/gLkuJ0RprE6xNscZikglCWWazGc/yeX62aty5ewsvikQ2HOmE6n6N+OAd0phse0bonj2eUu60qVWKNHc/U23u0f3+ClHf4c8cnufzFN/skLtxE8/5tpCvEUIgYx+pdearI7XOgu4hYbfDcdhnOOgy+DVgFDSJB0ecnM7ZerTF5tWXXLtyHc8RLTxSmCQl6vsMK2+JghZKGWpBiH1wm5PCE/RkitFnXidjtElIkoR2q037q0+5WMFzqWaESmVDUa/B8McLouYntJJ88UP62w8ZV0vIJM3m1sPRpGnKlN+EcfgvlJXCXpvj0mtCp7JTp+qH+JWPaHs+6XUoqbKg/K6P5+QvD4xNEIOAqFFCHJaRfp39fsxwr4QRMdpcTuqe3uv1nML1A+P8SSfZt9DJmNSFkk7OferLCYMgwHPJmoubM58WUKte/5ew0WjyFxc6K8nB1bJ+AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2024-01-14-webinar.png" srcset="/static/aeaac4b117eefd05e7a7793b42fef48d/36ca5/2024-01-14-webinar.png 200w, /static/aeaac4b117eefd05e7a7793b42fef48d/a3397/2024-01-14-webinar.png 400w, /static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2024-01-14-webinar.png 800w, /static/aeaac4b117eefd05e7a7793b42fef48d/e4699/2024-01-14-webinar.png 957w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p>Group checkout can be run as a series of single guest checkouts. We can complete it when all single ones are completed. Thus, we must record and accrue the information in the main process. We do that by subscribing to <em>GuestCheckoutCompleted</em> events and storing <em>GuestCheckoutCompletionRecorded</em> in the group checkout stream. When the last checkout for the group was made from this action, you will also get the <em>GroupCheckoutCompleted</em> event. It doesn’t make much sense to group those two events into <em>GuestCheckoutRecordedAndGroupCheckoutCompleted</em> event. We’re recording information about two different business facts.</p> <p><strong>I’d use multiple events if they represent different parts of the business process, especially if they’re optional.</strong></p> <p>Of course, it’s a grey area. My safe default is to record a single event. From my experience, most cases are like that. We should double check if there’s no benefit of having multiple events. Yet, I understand that someone may have a different perspective, so think for yourself.</p> <p>I started with base classes, super granular events, and sharing data between events, but I evolved from that. And I regret that I did that because it took me a lot of time to refactor that. The hidden coupling is a big enabler for accidental complexity and <a href="/en/how_to_solve_complicated_problems/">overly complicated solutions</a>.</p> <p><strong>Of course, it’s not my intention to push you to <a href="/en/state-obsession/">state obsession</a>.</strong> Having two events like <em>RoomReserved</em> and <em>GuestsInformationUpdated</em> is better than having CRUD-like <em>RoomReservationCreated</em> and <em>RoomReservationUpdated</em> events. I intend to suggest you watch your business process and reflect on it as it is. Don’t optimise for reusability.</p> <p><a href="/en/events_should_be_as_small_as_possible/">Events should be as small as possible but not smaller</a>. And remember to have a split between <a href="/en/internal_external_events/">internal and external events</a>. Thanks to that, you can keep the internal events precise and enrich them for external subscribers who need more context. <a href="/en/event_transformations_and_loosely_coupling/">Event transformations can help you</a> to keep your processes loosely coupled.</p> <p>Read also more in <a href="/en/on_putting_stream_id_in_event_data">On removing prefixes and stream ids from event data</a>. I continued there, the considerations around optimising for the code size and process understanding.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Check also <a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a></p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Not all issues are complex, some are complicated. Here's how to deal with them]]>https://event-driven.io/en/how_to_solve_complicated_problems/https://event-driven.io/en/how_to_solve_complicated_problems/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6d5d15a913a9a3c44212fbe6e892cfef/a331c/2024-01-05-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4jAAAuIwF4pT92AAADJElEQVQozwEZA+b8AK6tkMPDrcjGt8jFs9/e0cnNtpmne5Sod46zb5q8eqeloZSQj42Kh4B9eYaDg3Zxbn99gI2MjpWTkpmXlgCrqoy6uKXQzcLS0cPf4NO0tqeLlnrAyL+6zbWkxZGioZyUkI+LiYmKiIyBgYaFhYmVl5+eoaugoKWmpqkAwcCoycWxycnJsLK5pKWrf32CbmpyjI2bjIudqam2pqaomJWSnp6inZ6jlZeenaGpqKuysbG2ra+1rK2yAMbEpZGOhWFjc4GGlY+SnWFia0E/RjAoJUdGT1pcaW5rbYeCfo+LiZ2YlbWzr7u7u8nBt8K4qcO+t7e0sQCKioUqJywbGB1zdYOCgoklJCgaFhk3MDNCQEk9PUhOUWJ4eoSEf32MhoGto5DDtqGgiWV8ZUa7rpu+t64AREVUFhQXGRMTLCgrKB4cIBQQHRQRIBgWNi8xNTA1UVNhanKJfXt+lY2FpqGcr5+If2Q8MCEPmYlxxL2zAC0uOBIPEBwZGxsYHSgeHUQzLEc1LiwjIjItMCwmKVBQXXVze2hiYYqGgqugk6eUdpV+Vn5qTqubhsjDvgAPDhENCQgwLjUlIiczLC5SQTtoT0E/MCgfGRohGBY6MTJxb3J4cGqJhoWWi3yunIKumn6zoovDvLPEwb4ABgMDIBweLCgsJyMnOzU6U0U/TDguDwoHCwcGCwgJIBkXUUtIX1pZdXBtjod/n5OEtKqbxcC3trOuqqipAAYDAzQxNywnKyIcHTMvM0g9O0AyLhMQERUQEAoKDh0aGkc9N1hSUXp2d353cJiPhKSdlbawqqyqq52dogAVEhQzMTcgGhofFRIuKChANDA2LCoeHCAjHiAUExchHR9HPjtMRkJzZ1d/dm2Ce3WYko2oo5+9u7usrbIAHxseJiAgKhwXOColJBsbKR4bJBsaIR0fOjIzGxsgLSgqTUVAVlBNYllSYlpXfHZwnJeRqKSjpqSjoJybAB0XFjIlITAhHC4hGxoSEBsTEiAZGEU8OEc/PxwcIjUwMlRMR1tUUFlUU1xXWYN+fJqVkbe0squopp+cmfM+QP+0T9cSAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/6d5d15a913a9a3c44212fbe6e892cfef/a331c/2024-01-05-cover.png" srcset="/static/6d5d15a913a9a3c44212fbe6e892cfef/36ca5/2024-01-05-cover.png 200w, /static/6d5d15a913a9a3c44212fbe6e892cfef/a3397/2024-01-05-cover.png 400w, /static/6d5d15a913a9a3c44212fbe6e892cfef/a331c/2024-01-05-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://en.wikipedia.org/wiki/Cynefin_framework">Cynefin’s framework</a> states that we have four types of decision-making contexts (or <em>domains</em>): clear, complicated, complex, and chaotic.</strong> They also add confusion to the mix. And that sounds like a fair categorisation of the problems we face.</p> <p>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.</p> <p><strong>Complex problems are called <em>unknown unknowns</em>.</strong> This is the place where we feel creative; we do an explorer job. We probe sense and respond. The <em>design emerges</em> and <em>Agile shines</em>. We solve it, and then we go further into the sunset scenery like a lonesome cowboy. Off we go to the next exciting problem.</p> <p><strong>Complicated issues, on the other hand, are <em>known unknowns</em>.</strong> 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 <em>what</em> we need to do, but we need to find an exact <em>how</em> to solve it. Unknowns are more tactical than strategic.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/28499db876d1fcd8e29a67ceff9d234f/17f09/2024-01-05-cynefin.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 88.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAASABQDASIAAhEBAxEB/8QAGAABAQADAAAAAAAAAAAAAAAAAAECAwX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHtmJtBQUH/xAAZEAEBAAMBAAAAAAAAAAAAAAABAAIQMSL/2gAIAQEAAQUCyWVjmU+Y5v8A/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAFxAAAwEAAAAAAAAAAAAAAAAAABAhIP/aAAgBAQAGPwIjmf/EABwQAQACAgMBAAAAAAAAAAAAAAEAERAxIVFhgf/aAAgBAQABPyECVCfUsi9xIlE6G/ZywNY//9oADAMBAAIAAwAAABD3yAD/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAaEAEAAgMBAAAAAAAAAAAAAAABESEAMUEQ/9oACAEBAAE/EDY1trAOoQ1jS4XWGyM7tw7XKVljQUReLPgChHn/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Cynefin. Source: https://en.wikipedia.org/wiki/Cynefin_framework#/media/File:Cynefin_framework_2022.jpg" title="Cynefin. Source: https://en.wikipedia.org/wiki/Cynefin_framework#/media/File:Cynefin_framework_2022.jpg" src="/static/28499db876d1fcd8e29a67ceff9d234f/c60e9/2024-01-05-cynefin.jpg" srcset="/static/28499db876d1fcd8e29a67ceff9d234f/37402/2024-01-05-cynefin.jpg 200w, /static/28499db876d1fcd8e29a67ceff9d234f/4cda9/2024-01-05-cynefin.jpg 400w, /static/28499db876d1fcd8e29a67ceff9d234f/c60e9/2024-01-05-cynefin.jpg 800w, /static/28499db876d1fcd8e29a67ceff9d234f/6c738/2024-01-05-cynefin.jpg 1200w, /static/28499db876d1fcd8e29a67ceff9d234f/56dca/2024-01-05-cynefin.jpg 1600w, /static/28499db876d1fcd8e29a67ceff9d234f/17f09/2024-01-05-cynefin.jpg 2250w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>And here’s the funny thing: we too often feel more comfortable solving complex tasks than complicated ones.</strong> We counterintuitively think, <em>“Well, we just need to do research, then select a solution and solve it”</em>. Too often, that’s our self-defence. We’re tricking ourselves by postponing the issue. It’s easier to justify our efforts in research than lag on <em>known unknowns</em>.</p> <p>If we categorise something as complex, then probably no one from our close circle knows the answer. There’s less chance that we’ll be criticised. For complicated, there’s a feeling that someone can always come and say <em>“It is an easy task; why are you spending so much time on it?”</em>. Yes, <em>complicated</em> is a subjective term. It’s like an inverted <em><a href="https://positivepsychology.com/instant-gratification/">Instant Gratification Theory</a></em>; we’re delaying potential bad scenarios.</p> <p><strong>Some say that complex tasks will give more benefits, as only a few people can solve them.</strong> That’s partially true, but there’s also a reason why they’re complex, and we don’t know if we’re <em>that person</em> able to solve it. As I mentioned, we tend to downplay complex problems, and accidental complexity kills our efforts.</p> <p><strong>We already learned that complicated tasks are perceived as boring, and we tend to avoid them.</strong> They usually reflect those annoying bugs killing our product by 1000 paper cuts.</p> <p>Let’s say that we have two tasks to choose from. One is complex, and the other is complicated. They both have a similar business value. Shouldn’t we choose the one with <em>known unknowns</em>?</p> <p>Let’s say we should, but how do we tackle complicated tasks? I’ll show you how I did it with the example.</p> <p><strong><a href="https://martendb.io/">Marten</a> is the open-source project I’m working on.</strong> It allows using PostgreSQL as a Document Database and Event Store. Our goal is to help remove boilerplate work. Thus, we provide multiple ways of dealing with multitenancy out of the box. We have two major options <a href="https://martendb.io/documents/multi-tenancy.html">with tenant column in each table level</a>, <a href="https://martendb.io/configuration/multitenancy.html">database per tenant</a> if you need complete data separation. We’ll discuss the issue with the first option.</p> <p><strong>With Marten, you can do the following code to configure Marten to use the basic tenancy on the table level:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> store <span class="token operator">=</span> DocumentStore<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span><span class="token function">Connection</span><span class="token punctuation">(</span><span class="token string">"some connection string"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// The events are now multi-tenanted</span> options<span class="token punctuation">.</span>Events<span class="token punctuation">.</span>TenancyStyle <span class="token operator">=</span> TenancyStyle<span class="token punctuation">.</span>Conjoined<span class="token punctuation">;</span> <span class="token comment">// And all documents also</span> options<span class="token punctuation">.</span>Policies<span class="token punctuation">.</span><span class="token function">AllDocumentsAreMultiTenanted</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It’ll define the <em>TenantId</em> column for events and each document table. It’ll be used to discriminate all operations, so you accidentally won’t change data from another tenant.</p> <p>You can set up a session for the tenant as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> session <span class="token operator">=</span> store<span class="token punctuation">.</span><span class="token function">OpenSession</span><span class="token punctuation">(</span><span class="token string">"SomeTenantId"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then, all operations will include this tenant id as an additional parameter.</p> <p><strong>And that’s the perfect world; the reality is much more complicated.</strong>.</p> <p>For instance, you’re building a SaaS platform like Booking.com. Almost all data will be specific for tenants, but you have some global data that can use various tenants; for instance, users can book nights in multiple hotel chains. You might also want to have global reports showing all tenants’ aggregations.</p> <p>Moreover, some may have admin operations that should update data from multiple tenants.</p> <p>Of course, being an Open Source maintainer, you may ignore such feature requests and not provide support; that’s some solution. But if you want to keep your community thriving and your tool helping users, you accept such feature requests.</p> <p>Some time ago, we provided the option to create a nested session for different tenants. You could, for instance, do the following code:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// Create a global session with no tenant</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> session <span class="token operator">=</span> store<span class="token punctuation">.</span><span class="token function">OpenSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create user globally</span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">UserRegistered</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Create a nested session for the tenant</span> <span class="token class-name"><span class="token keyword">var</span></span> nestedSession <span class="token operator">=</span> session<span class="token punctuation">.</span><span class="token function">ForTenant</span><span class="token punctuation">(</span>hotelChainId<span class="token punctuation">)</span><span class="token punctuation">;</span> nestedSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">RoomReserved</span><span class="token punctuation">(</span>roomId<span class="token punctuation">,</span> hotelId<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That looks simple enough; indeed, it’s not extremely complicated if we consider only writes. Marten has built-in <a href="https://martendb.io/documents/sessions.html#unit-of-work-mechanics">Unit of Work</a>, which stores all pending changes in the same transactions. In the case above, it’ll append the <em>UserRegistered</em> event as global data (using default tenant) and <em>RoomReserved</em> to the hotel chain tenant.</p> <p><strong>Stuff gets more complex if we consider projections.</strong> Marten can build read models based on the stored events (read more in <a href="/en/projections_in_marten_explained/">Event-driven projections in Marten explained</a>). Projections can be inline and async. Inline is run within the same transaction as appended events, and async is the background process. And now, things start to get complicated:</p> <ul> <li>an event can update multiple projections,</li> <li>an event with a default tenant can update projections with a default tenant (so global data). (We don’t support updating tenants based on the global data, as this could create a <a href="https://en.wikipedia.org/wiki/Ripple_effect">ripple effect</a>).</li> <li>an event with a non-default tenant can update projections with non-default and default tenants. The last one helps in global summaries (e.g., a report of the number of reservations from all chains).</li> <li>a single unit of work can have events from multiple tenants (both default and non-default).</li> </ul> <p><strong>Not complicated enough? Be my guest!</strong> <a href="/en/scaling_out_marten">Async projections</a> are a fancy beast. We want to make the processing efficient, so we optimise them. Each projection type (e.g. <em>UserProfileDetails</em>, <em>RoomReservation</em>, etc.) is processed in parallel. We poll batches of events for each projection type based on the event types it supports. Thanks to that, we only read needed events and limit the number of queries. We’re also grouping events by tenant id and the read model id. Thanks to that, if there are a few events in the batch updating a single read model, we’ll load it only once, apply those events, and store the result as a single update statement. And that’s just a brief description of our optimisations and potential permutations.</p> <p>Coming back to our multitenancy and its impact on async projections. If we group events by tenant, we’re grouping them based on the tenant it was appended to (e.g. hotel chain id or default one). That’s fine for primary usage, but you already know that the read model can have different tenants than the events (e.g. global reports). For such a case, we must open a nested session. And one of our users <a href="https://github.com/JasperFx/marten/issues/2363">spotted inconsistency</a>. Gulp.</p> <p><strong>I realised that such an approach requires a systematic approach.</strong> If I just tried to sit and fix it, then my work would look like in this famous Peter Griffin scene:</p> <p><img src="/ef7f05db54e33e238798c2408f85dbfa/2024-01-05-blinds-venetian.gif" alt="Peter Griffin venetian blinds"></p> <p><strong>To have that under control, I needed:</strong></p> <ul> <li>write up the permutations,</li> <li>have the centralised code for deciding whether to open nested session or not,</li> <li>have that code unit-testable, and write clear tests for that.</li> </ul> <p>The best way to write up permutations is to write them up in table. I did it like that:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">|---------------------------------------------------------| | SCENARIOS | |---------------------------------------------------------| | SESSION | SLICE | STORAGE | RESULT | |-------------|-------------|-----------|-----------------| | DEFAULT | DEFAULT | SINGLE | THE SAME | | DEFAULT | DEFAULT | CONJOINED | THE SAME | | DEFAULT | NON-DEFAULT | SINGLE | THE SAME | | DEFAULT | NON-DEFAULT | CONJOINED | NEW NON-DEFAULT | | NON-DEFAULT | DEFAULT | SINGLE | NEW DEFAULT | | NON-DEFAULT | DEFAULT | CONJOINED | THE SAME | | NON-DEFAULT | NON-DEFAULT | SINGLE | NEW DEFAULT | | NON-DEFAULT | NON-DEFAULT | CONJOINED | THE SAME |</code></pre></div> <p>Where:</p> <ul> <li><strong>Session</strong> - original processing session tenancy (default tenant or non-default)</li> <li><strong>Slice</strong> - the tenancy of grouped batch of events (default tenant or non-default)</li> <li><strong>Storage</strong> - the tenancy of the read model (<em>Single</em> - only default tenant is allowed, <em>Conjoined</em> - non-default tenants are allowed).</li> <li><strong>Result</strong> - either the same session can be used, or a new nested one should be created (with default or non-default tenant).</li> </ul> <p>You can also see a pattern in which I described permutations. I started by adding possible values for the first column (<em>DEFAULT</em> and <em>NON-DEFAULT</em>), then for each value, I added the permutations for the second column and then did the same for the third column. Thanks to that, I’m sure that I covered all permutations. It’s also easier to read and reason if we see bigger groups first and then smaller subgroups in the following columns.</p> <p><strong>Having all permutations, I filled in the result.</strong> That’s the part that cannot be done automatically. You need to sit and think of the expected result based on the constraints you have. If it’s about complicated business logic, then it’s time to consult your domain expert to ensure that you understand that well. Once you confirm it, such a breakdown will be an excellent input for unit tests.</p> <p>Preparing a permutations table sounds straightforward, but it does not always have to be like that. We might get so many permutations or additional edge case constraints that such a table would be too big to give clear answers. The art in that is to decide which scenarios are essential and which are edgy. In my case, I decided to ignore some conditions like feature flags defining whether we allow multi-tenancy, global projections, and if we should keep tenants in the same database (we also allow <a href="https://martendb.io/configuration/multitenancy.html">database per tenant</a>). Of course, I’ll need to include them in the final implementation and the set of test scenarios, but they’re not changing the big picture; they’re switching some settings. Adding them to the permutations table would blur the picture and not help enhance understanding.</p> <p>Based on that, I prepared the implementation:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">TenantedSessionFactory</span> <span class="token punctuation">{</span> <span class="token comment">// I also placed permutation table here as a comment, </span> <span class="token comment">// but skipped it in this snippet for brevity</span> <span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token return-type class-name">DocumentSessionBase</span> <span class="token function">UseTenancyBasedOnSliceAndStorage</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">DocumentSessionBase</span> session<span class="token punctuation">,</span> <span class="token class-name">IDocumentStorage</span> storage<span class="token punctuation">,</span> <span class="token class-name">IEventSlice</span> slice <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> shouldApplyConjoinedTenancy <span class="token operator">=</span> session<span class="token punctuation">.</span>TenantId <span class="token operator">!=</span> slice<span class="token punctuation">.</span>Tenant<span class="token punctuation">.</span>TenantId <span class="token operator">&amp;&amp;</span> slice<span class="token punctuation">.</span>Tenant<span class="token punctuation">.</span>TenantId <span class="token operator">!=</span> Tenancy<span class="token punctuation">.</span>DefaultTenantId <span class="token operator">&amp;&amp;</span> storage<span class="token punctuation">.</span>TenancyStyle <span class="token operator">==</span> TenancyStyle<span class="token punctuation">.</span>Conjoined <span class="token operator">&amp;&amp;</span> session<span class="token punctuation">.</span>DocumentStore<span class="token punctuation">.</span>Options<span class="token punctuation">.</span>Tenancy<span class="token punctuation">.</span><span class="token function">IsTenantStoredInCurrentDatabase</span><span class="token punctuation">(</span> session<span class="token punctuation">.</span>Database<span class="token punctuation">,</span> slice<span class="token punctuation">.</span>Tenant<span class="token punctuation">.</span>TenantId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shouldApplyConjoinedTenancy<span class="token punctuation">)</span> <span class="token keyword">return</span> session<span class="token punctuation">.</span><span class="token function">WithTenant</span><span class="token punctuation">(</span>slice<span class="token punctuation">.</span>Tenant<span class="token punctuation">.</span>TenantId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> isDefaultTenantAllowed <span class="token operator">=</span> session<span class="token punctuation">.</span>SessionOptions<span class="token punctuation">.</span>AllowAnyTenant <span class="token operator">||</span> session<span class="token punctuation">.</span>Options<span class="token punctuation">.</span>Advanced<span class="token punctuation">.</span>DefaultTenantUsageEnabled<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> shouldApplyDefaultTenancy <span class="token operator">=</span> isDefaultTenantAllowed <span class="token operator">&amp;&amp;</span> session<span class="token punctuation">.</span>TenantId <span class="token operator">!=</span> Tenancy<span class="token punctuation">.</span>DefaultTenantId <span class="token operator">&amp;&amp;</span> storage<span class="token punctuation">.</span>TenancyStyle <span class="token operator">==</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shouldApplyDefaultTenancy<span class="token punctuation">)</span> <span class="token keyword">return</span> session<span class="token punctuation">.</span><span class="token function">WithDefaultTenant</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> session<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">DocumentSessionBase</span> <span class="token function">WithTenant</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> tenantId<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>DocumentSessionBase<span class="token punctuation">)</span>session<span class="token punctuation">.</span><span class="token function">ForTenant</span><span class="token punctuation">(</span>tenantId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">DocumentSessionBase</span> <span class="token function">WithDefaultTenant</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>DocumentSessionBase<span class="token punctuation">)</span>session<span class="token punctuation">.</span><span class="token function">ForTenant</span><span class="token punctuation">(</span>Tenancy<span class="token punctuation">.</span>DefaultTenantId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see, there are some <em>weird ifs</em>; I told you it’s complicated. Still, it’s manageable and much more straightforward than it could be, especially if <a href="https://github.com/JasperFx/marten/blob/f85f61be0ad1cfe2d24d60217bdae061c098b19b/src/Marten/Sessions/TenantedSessionFactory.cs#L10-L22">we keep the permutation table together with the code</a>. That’s actually good example of the helpful comment in code.</p> <p>I also added potentially redundant wrappers <em>WithTenant</em> and <em>WithDefaultTenant</em>. They’re just wrapping single-line code calls, but I wanted to highlight the intention explicitly. The same I did for additional variables like <em>shouldApplyConjoinedTenancy</em>, <em>isDefaultTenantAllowed</em>, and <em>shouldApplyDefaultTenancy</em>. I could probably zip it more, but thanks to them, when reading code for the first time, you might not need to understand those <em>weird</em> ifs fully, but read what they’re checking thanks to clear variable names.</p> <p>I also used <em>inverted ifs</em>. Instead of nesting <em>else</em> conditions, I’m returning the result as fast as possible. In such an approach, the important part is keeping the default, expected option as the last. In our case, that’s reusing existing sessions, and that’s reflected in the code. That also helps the reader, as they can first scroll down to see what, by default, is returned and then check edge cases.</p> <p>Yes, I didn’t write tests first. I have to confess that I never got fully into the habit of doing that. Usually, I start with a raw draft of the API, and then I do the red/green sandwich. I feel more efficient by doing that, especially when I need surgery on already existing, complicated code. Still, if you prefer to follow fully TDD, that’s also a viable option.</p> <p><strong>I also wanted tests to be explicit about the intention.</strong> If we have such repeatable code varying on the permutation of inputs, it’s good to do <a href="https://fsharpforfunandprofit.com/pbt/">Property-based testing</a>. So, preparing (or generating) a set of inputs ensures that they give an expected result. In Marten, we’re using the XUnit framework, which allows us to do it through <a href="https://andrewlock.net/creating-parameterised-tests-in-xunit-with-inlinedata-classdata-and-memberdata/"><em>Theory</em> attribute</a>. Sidenote: that’s not <em>precisely property-based testing</em> (more <em>parametrised test</em>), as we’re manually setting the values instead of letting the testing framework do that. Yet, the intention is the same. We’re testing specific properties of our system by providing different sets of values testing various paths of the same flow.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TenantedSessionFactoryTests</span> <span class="token punctuation">{</span> <span class="token comment">// here I also placed permutation table as comment</span> <span class="token punctuation">[</span>Theory<span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">MemberData</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>Configurations<span class="token punctuation">)</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Verify</span><span class="token punctuation">(</span><span class="token class-name">Configuration</span> setup<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Given</span> <span class="token class-name"><span class="token keyword">var</span></span> session <span class="token operator">=</span> <span class="token function">SessionWith</span><span class="token punctuation">(</span>setup<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> slice <span class="token operator">=</span> <span class="token function">SliceWith</span><span class="token punctuation">(</span>setup<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> storage <span class="token operator">=</span> <span class="token function">StorageWith</span><span class="token punctuation">(</span>setup<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// When</span> <span class="token class-name"><span class="token keyword">var</span></span> newSession <span class="token operator">=</span> session<span class="token punctuation">.</span><span class="token function">UseTenancyBasedOnSliceAndStorage</span><span class="token punctuation">(</span>storage<span class="token punctuation">,</span> slice<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Then</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>setup<span class="token punctuation">.</span>ExpectsNewSession<span class="token punctuation">)</span> <span class="token punctuation">{</span> newSession<span class="token punctuation">.</span><span class="token function">ShouldBe</span><span class="token punctuation">(</span>session<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> newSession<span class="token punctuation">.</span><span class="token function">ShouldNotBe</span><span class="token punctuation">(</span>session<span class="token punctuation">)</span><span class="token punctuation">;</span> newSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ShouldBeOfType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>NestedTenantSession<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> newSession<span class="token punctuation">.</span>TenantId<span class="token punctuation">.</span><span class="token function">ShouldBe</span><span class="token punctuation">(</span>setup<span class="token punctuation">.</span>ExpectedNewSessionTenantId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">TheoryData<span class="token punctuation">&lt;</span>Configuration<span class="token punctuation">></span></span> Configurations <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// (...) wait for that</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Thanks to that, the test code is the same. We’re building the inputs based on the configuration and then checking if we got the expected result.</p> <p><strong>Beware here not to fall into a trap providing the test for everything.</strong> Such an approach is tempting to overgeneralise testing scenarios. I’ve used a single test method here, as my code is just effectively making a single decision: use an existing session or create a new one. The code is not complex but complicated because of the permutations of the input and conditional logic. If you have more scenarios, express each scenario in the dedicated test. In the past, I felt the trap and abused this approach, ending with tests that would require tests to verify that they’re testing code correctly… Beware, and don’t go down that path. Tests are a form of documentation, and duplication is, in general, okay for them.</p> <p>Let’s check how the test configuration is setup:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">TheoryData<span class="token punctuation">&lt;</span>Configuration<span class="token punctuation">></span></span> Configurations <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">TheSame</span><span class="token punctuation">(</span>Default<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">TheSame</span><span class="token punctuation">(</span>Default<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Conjoined<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">TheSame</span><span class="token punctuation">(</span>Default<span class="token punctuation">,</span> NonDefault<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">New</span><span class="token punctuation">(</span>Default<span class="token punctuation">,</span> NonDefault<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Conjoined<span class="token punctuation">,</span> NonDefault<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">TheSame</span><span class="token punctuation">(</span>Default<span class="token punctuation">,</span> NonDefault<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Conjoined<span class="token punctuation">,</span> <span class="token named-parameter punctuation">isTenantStoredInCurrentDatabase</span><span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">New</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">,</span> Default<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">New</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> <span class="token named-parameter punctuation">allowAnyTenant</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">defaultTenantUsageEnabled</span><span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">New</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> <span class="token named-parameter punctuation">allowAnyTenant</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">defaultTenantUsageEnabled</span><span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">TheSame</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">,</span> <span class="token named-parameter punctuation">allowAnyTenant</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">defaultTenantUsageEnabled</span><span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">TheSame</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Conjoined<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">New</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> NonDefault<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">,</span> Default<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">New</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> NonDefault<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> <span class="token named-parameter punctuation">allowAnyTenant</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">defaultTenantUsageEnabled</span><span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">New</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> NonDefault<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">,</span> Default<span class="token punctuation">,</span> <span class="token named-parameter punctuation">allowAnyTenant</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">defaultTenantUsageEnabled</span><span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">TheSame</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> NonDefault<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Single<span class="token punctuation">,</span> <span class="token named-parameter punctuation">allowAnyTenant</span><span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">defaultTenantUsageEnabled</span><span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">TheSame</span><span class="token punctuation">(</span>NonDefault<span class="token punctuation">,</span> NonDefault<span class="token punctuation">,</span> TenancyStyle<span class="token punctuation">.</span>Conjoined<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Each row will run as a separate test with the provided configuration. I provided firstly the expected result, so <em>TheSame</em> or <em>New</em> session. Then, I’m passing additional configuration. The first three params come directly from our permutation table and optional params (<em>allowAnyTenant</em>, <em>defaultTenantUsageEnabled</em>) from those <em>edge casy</em> feature flags.</p> <p>The configuration is encapsulated into a dedicated configuration class, and <em>TheSame</em> and <em>New</em> are static factory methods preparing it. I added them to make the setup explicit and encapsulate it.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Configuration</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> SessionTenant<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> SliceTenant<span class="token punctuation">,</span> <span class="token class-name">TenancyStyle</span> StorageTenancyStyle<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> IsTenantStoredInCurrentDatabase<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> AllowAnyTenant<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> DefaultTenantUsageEnabled<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> ExpectsNewSession<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> ExpectedNewSessionTenantId <span class="token operator">=</span> <span class="token keyword">default</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Configuration</span> <span class="token function">TheSame</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> sessionTenant<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> sliceTenant<span class="token punctuation">,</span> <span class="token class-name">TenancyStyle</span> storageTenancyStyle<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> isTenantStoredInCurrentDatabase <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> allowAnyTenant <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> defaultTenantUsageEnabled <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> sessionTenant<span class="token punctuation">,</span> sliceTenant<span class="token punctuation">,</span> storageTenancyStyle<span class="token punctuation">,</span> isTenantStoredInCurrentDatabase<span class="token punctuation">,</span> allowAnyTenant<span class="token punctuation">,</span> defaultTenantUsageEnabled<span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Configuration</span> <span class="token function">New</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> sessionTenant<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> sliceTenant<span class="token punctuation">,</span> <span class="token class-name">TenancyStyle</span> storageTenancyStyle<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> expectedNewSessionTenantId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> allowAnyTenant <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> defaultTenantUsageEnabled <span class="token operator">=</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> sessionTenant<span class="token punctuation">,</span> sliceTenant<span class="token punctuation">,</span> storageTenancyStyle<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> allowAnyTenant<span class="token punctuation">,</span> defaultTenantUsageEnabled<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> expectedNewSessionTenantId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Of course, you may come up with other implementations. You may feel that it’s still not readable enough, and that’s fine. I want to give you the thought process and tools to reason with complicated problems; the implementation details may differ based on your preferences, the tools you use and the language you code.</p> <p>That’s also why I used the real scenario from the real project. It’s dirty and filled with tradeoffs, as complicated scenarios are. See the full code in the <a href="https://github.com/JasperFx/marten/pull/2497">Pull Request</a>. It shows the complete scope of changes and all tests I added/updated. Plus, dirty stuff like mocking abstract class. You cannot fix the whole word with one change.</p> <p>And here are two final pieces of advice.</p> <p><strong>Don’t try to refactor together with complicated changes.</strong> Kent Beck said: <em>“Make the change easy, make the easy change”</em>. You’ll increase the accidental complexity if you try to perform broad refactoring and apply complicated changes. Such an approach usually ends up in a long-living branch that will never get merged. Don’t do that. Limit the scope of change; either do refactoring first or afterwards. Not at the same time.</p> <p><strong>Even with the best, methodic approach, you may miss something and make a mistake.</strong> What I described here helps to organise your thoughts, analyse the complicated scenario and make it more predictable. Yet, with the complicated problems and bigger existing codebase, we can still miss something. So do your best, but prepare for failure. What I described above was a decent solution, but not perfect. I missed one permutation and didn’t apply that everywhere; luckily, one of the users <a href="https://github.com/JasperFx/marten/pull/2764">spotted it and provided the fix</a>. The good thing was that <a href="https://github.com/JasperFx/marten/pull/2764/files#diff-3fcf954860d76bdf072e784f1f93009926c6822e4692d736515a5baae8bbbb66R54">I made the change easy</a>, as my previous work could be reused and test scenarios expanded.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[What Dune can tell us about setting our goals]]>https://event-driven.io/en/dune_and_long_term_goals/https://event-driven.io/en/dune_and_long_term_goals/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/22a285d61ac0be8c946272d7e3d58714/a331c/2023-12-30-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADfElEQVQ4yx3M209bBQCA8fNX+KJxCcq4t9xaC+XS0tMLXXtaCuWcltJ7Tw899LZS6pLJogMCBLYpOjUxIZsyMdNphs4tc5o4jdn0YZppFh/1BfVhamJ8UT/DHn753j7hiQ4nTT1eWswTtD4TotUUoNMSomtEoX00RtdYii5njm5nlj53lt5xjXZ7kpahKEYxTf94nnZbnCbzJE19AYRWq4JhLEmvK0u3K4fRkaLLFsPoSNJqT9HpymMY1zF6i5ilMpZQDaOvRJdTxeDIYPKoGOwJmiwKR/omEJpNIQ6nvY4U3gmdWaWIJ1jCLFUYDFYwSyV6vPOP9Etl+v0lescLGN0aRvccPR6NHjGN0T6LcSSK0GaN0OdSCU6VqaXn+XClzP5mDYtU4WmxSrOzylFXladsGk3DWZpHVDpFDbOvyHCwgtWvM+DJYnHEMY0oCEOuJMlple1ijrubGr/tlfn30wZ3duu8trHI6lKd2uKzzC8+R6VxgmJtETlXZ3SqRr+vyqBUYsCnY/Lk6RXTCKdkmbfnE3x+SuXumsadjTl+eFnnj3dLcKPKf58s8Nf1BR5eLfNwv8zBlRIPdst8+UaNRqVGh6hhELN02pK0DUURViMKW/EY51JxziQTLCkJXlGT3FvP8O1mhvtnMzw4r/Hr3nG+u9hg1FfA4laRQiqTYZX24VlarRHarFFaLDKC6g5Q8E2hSwp6MEYmmCAhpXg1n+azkxm+3y7w86Xj/Pl+nX8+qrPeqNDhUHnSkuaIOc7RwTitw0k6RxO0W2cQkqKfgjdENTBNYzLCkhzjTCbNXk3l9kqOb7Yy3Dub5sfzKgcXivx9ucJX20XWaxq5hIroT2O0JXncFOWxbhlBGhCJjnooeCSeD4fZyc9wvTHLrRNRri1EuLY4w62Tcb44neLrjQz3X9Q42NH5/S2dX3bm+On1LLdXErykKeQCEwjy0BjVYz7WIxNcyId5ryhzuSizq4W5qIZ5U5O5pMu8U1S4Uo1ytX4own5d4eO6zI26zM2FKW5Wg3ygHUNoSB6Ww162oj7OxSS2ZiTWFD+nwz5WZYlVJcBaJMhGNMh6NMCK7Gd52sfKtI/lKS8vTHpYCjhZ9NopiUMIMasZzTGC7rIz5xoj7xwjJx5WpOBxPaKPu5lzu1BdIllRJO1wkLKPkbTZmB0eJjI4SMhkwmMw8j+tyjGQRFedMQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/22a285d61ac0be8c946272d7e3d58714/a331c/2023-12-30-cover.png" srcset="/static/22a285d61ac0be8c946272d7e3d58714/36ca5/2023-12-30-cover.png 200w, /static/22a285d61ac0be8c946272d7e3d58714/a3397/2023-12-30-cover.png 400w, /static/22a285d61ac0be8c946272d7e3d58714/a331c/2023-12-30-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Dune series has a lot of great thoughts. They’re more than just sci-fi books. Let me share one with you:</p> <blockquote> <p><strong>Moneo:</strong> For what do you hunger, Lord?</p> <p><strong>Leto:</strong> For a humankind which can make truly long-term decisions. Do you know the key to that ability, Moneo?</p> <p><strong>Moneo:</strong> You have said it many times, Lord. It is the ability to change your mind.</p> <p><strong>Leto:</strong> In the view of infinity, any defined long-term is short-term.</p> <p><strong>Moneo:</strong> There are no rules at all, Lord?</p> <p><strong>Leto:</strong> Perhaps one. Short-term decisions tend to fail in the long term.</p> </blockquote> <p><strong>It’s a concise summary of why neither big upfront designs will work nor design will magically emerge.</strong></p> <p>The former is not flexible and will not stand the proof of time and the changing environment.</p> <p>The latter will eventually be swamped by short-termism.</p> <p>The best thing is to cherish curiosity and be humble, assuming we’ll be wrong but can recover and adjust our course.</p> <p>I’m not always succeeding in that. But failure is coherent to what we do.</p> <p>That applies to architecture, but not only.</p> <p><strong>That’s also why I don’t set short-term goals;</strong> the year is short-term. I’m trying to define where I’d like to be and think about what I should do to get there.</p> <p>For instance, my recent goal was to have more time for my family and have flexible time. That’s why <a href="/en/leaving_event_store/">I chose to be a consultant</a>, and for now, this gets me closest to that. Being a consultant is not a goal for me but a tool.</p> <p><strong>Actually, I’m not a consultant; I’m working as a consultant.</strong> It is not who I am but what I do. I worked as a technical team leader for most of my career, but I no longer do that. I might also stop working as a consultant if that no longer matches my goals.</p> <p>In the same sense, our system is not equal to our architecture. This is how it works, but what it is should be described in business terms. Our goal should always be related to business; we should treat our design, decisions, and technology choices as a tool, not a goal per se.</p> <p>That should help us be flexible, revisit our decisions, and adjust them, as we won’t be so attached to them if they’re just tools. Changing the tool is easier than a goal.</p> <p>By the way, sand dunes are an excellent example of the difference between the importance of short-term and long-term decisions. Sand is moved left and right by the wind. Dunes change their place, height, and length; they change the form, but they’re still dunes. Even a giant sandstorm won’t change that. Still, they might erode and transform into different forms if the environment changes significantly, but that takes time.</p> <p><strong>I encourage you to read regular books.</strong> They can tell us more about life and ourselves than business and self-help books. The Dune series can teach us a lot about strategy and the impact of our decisions; George Orwell’s 1984 and Isaac Asimov’s Foundation as well.</p> <p><strong>I also wrote about it in the past, see:</strong></p> <ul> <li><a href="/en/removability_over_maintainability/">Removability over Maintainability</a></li> <li><a href="/en/how_to_design_software_architecture_pragmatically/">How to design software architecture pragmatically</a></li> <li><a href="/en/the_risk_of_ignoring_risks/">The risk of ignoring risks</a></li> <li><a href="/en/why_are_we_afraid_of_our_decisions/">Why are we afraid of our decisions?</a></li> <li><a href="/en/chesterton_fence_and_software_architecture/">What do the British writer and his fence have to do with Software Architecture?</a></li> <li><a href="/en/when_agile_is_not_enough/">When Agile is not enough</a></li> <li><a href="/en/holy_graal_syndrome/">The Holy Grail syndrome</a></li> <li><a href="/en/stacking_the_bricks/">Stacking the bricks in the software development process</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Why I won't use .NET Aspire for now]]>https://event-driven.io/en/nay_to_aspire/https://event-driven.io/en/nay_to_aspire/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4121642a7f4fcf1b2cc6e8d20318ca55/a331c/2023-12-21-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADeUlEQVQ4yz2TW2ibBRiG/6VN0xz6Z2ny/zk2xyZNmvw5NIcmf5I2TUzaxHZtVrEdQ123Tt1KC05aO5jUw2QKMi+0iKIb7EqpmyJzFx5uBB3qhQiDgReCAxmKd94IXjxiRC++i4/35f1eHvgE45CJwxYzkl2i2pim2qrjj0Zxh4JIHg+S24VslxjQG/FHImRLBXJqEYss4/L5iChxChWV5dVF6s0KQjhgZzIVIDbmJ1spUe+0sHt92FxuArExnD4/mn4tg3o9wWiURDZFPJ/jkM6IzijS6jTpdDvUmnWypUmEejHCajtHLptANySSnsyTKZeoNOvkqio2pwuT2YLV6SBfKdFemGO2pnLh7HEuXdjg1+8PeOXpEyQnkvhHQwjtcpSjrTyhRAzRZkNyOZmea6E2aoylk1hkOyOhIKIs06iV+eX2e9y/eZmf3t3l5w9f5ZvXttg5Oo0v6EMUzQhLMynUYgZPOEx4PMaA3kA0lSRfVZE9HpxeL6PxcQSNlqsvbnHnnfN8fXGdz58/ybUzXc61VZyyFaPJ1MMiVEpJYtkM8Yk0Tr8fjXYAnV6P7HHj8nlJTuZ6GAaGROYKCtlElPXZMgebK1w8voDXYUfo02IwGHF5nAixTIKwMs5IOEQ4EsByeAhBENDpBxmWbPjCIXyjoR4OrdFE/7DM2myJl+ZVrMO2nlc0i9hddsaiIYTxbJq0WuDJk11+//YKP36xT01NIQgaXCPuHj/J4SBTzOINBUmkU1zafoK397ZY7nZQEjECAR/DkhWDyYggu+xYnA6e21yBezfh3i3+vHuduansv9ctFhIZhZl2nWpzhkA4RLmqMjtXp9koU5sqUq2WsEhWBo0GhD6NBkE4RKekcP/6y/z21VX+unPAD7f2CYSDLB5bIlPJ98LN1mGGLBYMZnNv/2/6+vt7Df95EkE3OPC/8NELp/nyzV0+e32b7944x8cf7PPH3dt8unOWh7ttjqwuolbzlMpZlGSM0Kgff8CDKJqQZCvReATBHQwiuzxojSKP1vPcOP8YVzZXufHUQ3zy1mXeP/U415IKM7U6pUaDpeVFph+YYSweJ5pQCEQi6I0mJIcLZSKDsLtxivUTj9A+skQhV+BYQWEtr7CSV1jrLnFm7TQ7G5s8s7vN3t6zbG5t0XxwgeJUnWSuyFSjRSZfpFCZpjk/z99bEbcmWd6O8QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4121642a7f4fcf1b2cc6e8d20318ca55/a331c/2023-12-21-cover.png" srcset="/static/4121642a7f4fcf1b2cc6e8d20318ca55/36ca5/2023-12-21-cover.png 200w, /static/4121642a7f4fcf1b2cc6e8d20318ca55/a3397/2023-12-21-cover.png 400w, /static/4121642a7f4fcf1b2cc6e8d20318ca55/a331c/2023-12-21-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>When you’re angry, take a breath, take your time and then talk. So I did after I tested .NET Aspire yesteday.</strong> I hoped it could be a decent tool for local development with a decent synergy to Marten and Wolverine, and…</p> <p><strong>Unfortunately, I think that Aspire is not even usable for local development.</strong> Unless you go full Windows + Visual Studio.</p> <p>I was going back and forth on whether to write it, but I think people should see different opinions to make a proper judgement. That’s why I decided to balance the mostly hyped comments I saw. So let’s go.</p> <p><strong>The setup is overcomplicated.</strong> You need to <a href="https://learn.microsoft.com/en-us/dotnet/aspire/get-started/build-your-first-aspire-app?tabs=visual-studio#create-the-template-solution">add two additional projects</a>, one for the host and the other for “defaults”.</p> <p><strong>You need to have all projects in a single solution.</strong> And reference all startup projects to the .NET Aspire Host (or provide the relative paths to them). Ah, and add default project to all of them. That could be fine for a small monolithic solution but unrealistic for a bigger system.</p> <p><strong>Plus, cherry on top: I didn’t manage to configure the local environment on Ubuntu after spending a few hours on it.</strong></p> <p>Aspire tries reinventing the wheel instead of adapting to existing standard tools like Kubernetes. They came up with their own Control Plane, which you cannot just use by adding a NuGet package or running a Docker image. Nope, <a href="https://learn.microsoft.com/en-us/dotnet/aspire/fundamentals/setup-tooling?tabs=visual-studio#install-net-aspire">you need to install that on your local dev with <em>“dotnet workload install”</em></a>. Yes, there’s such a command.</p> <p>I don’t see myself as a total noob, but rather as an “average Joe”, but I didn’t manage to properly install Aspire tooling on my PopOS (Ubuntu-based) distro. Some may say that I just don’t know how to use it properly, but if that’s true, then that’s also true for most devs.</p> <p><a href="https://anthonysimmon.com/exploring-microsoft-developer-control-plane-core-dotnet-aspire-dotnet-8">Anthony Simmon made a great job investigating</a> and describing how bizarrely <em>Microsoft Developer Control Plane</em> works internally.</p> <p>Martin Thwaites experimented and <a href="https://github.com/martinjt/aspire-app-extension">created the NuGet package that self-hosts .NET Aspire Dashboard</a>. Which is how it should be done since the beginning. Yet, it’s unknown if .NET team will take this direction. If they don’t, we all know how using frameworks in unintended way always ends up.</p> <p>Of course, I could try it on my Windows machine, but if the DevOps tool doesn’t work easily on non-Windows boxes, that speaks a lot. It means that it’s not production-ready. I could probably spend more time trying to hack it, but then I’d need to consider how to make it repeatable on other people’s machines, automated setup in CI/CD, etc. That’s the opposite of what I’d expect from such a tool.</p> <p>Documentation is far from great, and the only way to troubleshoot is to try to skim through the source code, and hey, only part of it is open; <a href="https://github.com/dotnet/aspire/pull/941">the rest is closed</a>.</p> <p>I wasn’t a fan of <a href="https://github.com/dotnet/tye">Tye project</a>, whose successor is Aspire. For me, it was too bold to try to replace already established and working standards with something else. Unsurprisingly, it failed. And no, it wasn’t an experiment as it’s pictured now. <a href="https://www.youtube.com/watch?v=prbYvVVAcRs">I remembered how it was presented</a>, the same way as Aspire now. It seems that the .NET team took some lessons but made similar general mistakes.</p> <p><strong>Unfortunately, it looks like a demoware and project detached from the current development standards.</strong> I still have some (but small) hope that the .NET team will learn from the feedback and change it. Still, based on the current state, it would need to change fundamentally, which is hard to do at the time of the public preview.</p> <p><strong>I think that making it less ambitious in terms of revolution would work, so making .NET Aspire a “Spring Boot for .NET”.</strong> That’d be already a big win for a lot of projects.</p> <p>Currently, for hobby or small projects, it’s a no-go as the setup is overly complicated, and you can spend more time trying to make it work than it’s worth it; for the real project, it’s too buggy and detached from the open standards even to consider it.</p> <p><strong>TLDR. I didn’t have huge expectations, but I still got disappointed.</strong></p> <p><strong>EDIT:</strong> After posting this article, I had a good discussion with David Fowler and Reuben Bond under my <a href="https://www.linkedin.com/feed/update/urn:li:activity:7143511551008845824?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7143511551008845824%2C7143518529697349633%29&#x26;replyUrn=urn%3Ali%3Acomment%3A%28activity%3A7143511551008845824%2C7143604009860218880%29&#x26;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287143518529697349633%2Curn%3Ali%3Aactivity%3A7143511551008845824%29&#x26;dashReplyUrn=urn%3Ali%3Afsd_comment%3A%287143604009860218880%2Curn%3Ali%3Aactivity%3A7143511551008845824%29">LinkedIn post</a>. I appreciate that they took their time to discuss my concerns and provide their thoughts on it. Read more <a href="https://www.linkedin.com/feed/update/urn:li:activity:7143511551008845824?commentUrn=urn%3Ali%3Acomment%3A%28activity%3A7143511551008845824%2C7143518529697349633%29&#x26;replyUrn=urn%3Ali%3Acomment%3A%28activity%3A7143511551008845824%2C7143604009860218880%29&#x26;dashCommentUrn=urn%3Ali%3Afsd_comment%3A%287143518529697349633%2Curn%3Ali%3Aactivity%3A7143511551008845824%29&#x26;dashReplyUrn=urn%3Ali%3Afsd_comment%3A%287143604009860218880%2Curn%3Ali%3Aactivity%3A7143511551008845824%29">here</a> to see their perspective.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. To balance that check also a really good and concise <a href="https://www.youtube.com/watch?v=J02mvcEKrsI">introduction by Layla Porter</a>. You can see a draft of my try to apply <a href="https://github.com/JasperFx/marten/pull/2871">.NET Aspire on Marten sample appplication</a>.</p> <p>p.s.2 Note to future reader: I wrote this article at the time <a href="https://devblogs.microsoft.com/dotnet/announcing-dotnet-aspire-preview-2/">Preview 2 was just released</a>.</p> <p>p.s.3. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Hitchhiker's Guide To Moving From Relational Data To Events]]>https://event-driven.io/en/the_end_is_near_for_crud_data/https://event-driven.io/en/the_end_is_near_for_crud_data/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8b59709ef54e36c71a032a94903e9f03/a331c/2023-12-15-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAACaUlEQVQ4y42T30uTYRTH92/0Cwq6Cbrqqlsvi6DLCOmqi5CgEgoSzMyyi8KiTIooRCRLLcV0+GO5uc05t/k+53mfd5t756aphJQEhYMuP/FObVtBdXF4eA6Hz/k+3/Mcn9YaL0SkfBrj/LqLNliSR8TLCSLbtX8LXzVMKYtYNIgoC60NWsXIpO5jW+OIdtBek53afwAVxskwGxoh0HsWsRKIzpA142zm68nLHSzJobX6X+BOZzvLzMhV5kNPELOKSQ1SUK2Y1AtE59CiqKmvsms3fJWkwjaLxMKDmJmTZOxhguN9xPyXMdZwWfG2j/IHpDpX46GnwFNiEvdYTj/m40qO1aUAhXyRVHIBpXYUain7uT1EUzNUX203XVahJE1u0SWVSOG6a6ytWFgJP0rZO40FZdu4rotWwuRkgMHBIebn56ufXJFta2FleZkJ/yi5xVWKmUdYcy/Rtlv+AeKk0VOTLMzGGA4Eedf3msTAAEmlKsBdfzyVnp8eMBqOsLHxGSdxm2R8GttOo4whHQmTbb7KzNgYw0+fM3WzhWx7CzFtqoEVpZ4vji3EZsOIWiASGsJddBBLYbl5is86WDt3gmhwmlRrI5Gm66yfP4UJ+2uBlU0QOp8VeO9fIjjj0HBFGBkJkbYtFvJFCp132Tp9gHT/U0I9bbwZeMuX+uN8vVX3O1DKqxeJhLjU2ENn1wTJpKapuZ/u7j6y2TRKC+ttFyld2M+3hiN8engGZ/QV3+sPUbq290+gbdvE5+YITI0Sj0fJZjPMRj8Qj0UQt0ih9wGbDXWUOo7yo2UPpfbDlLqOUbqyj60bB/kJt/VXhTRErisAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/8b59709ef54e36c71a032a94903e9f03/a331c/2023-12-15-cover.png" srcset="/static/8b59709ef54e36c71a032a94903e9f03/36ca5/2023-12-15-cover.png 200w, /static/8b59709ef54e36c71a032a94903e9f03/a3397/2023-12-15-cover.png 400w, /static/8b59709ef54e36c71a032a94903e9f03/a331c/2023-12-15-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Knock knock? Who’s there? It’s me, Oskar, the end is near, did you know that?</strong> Ah, you know it but don’t know how to proceed? Let’s talk about the end of the CRUD world. Let’s provide a Hitchhiker’s Guide To Moving Relational Data To Events.</p> <p><strong>I hope that you know why the end is near. We’ve been trying to optimise the storage size; we’ve made some sins of overriding and losing our precious business data.</strong> Don’t worry; I already talked with Saint Peter, and he will pass us through. Those sins shall be forgiven, as we had no other choice. Yet, now we do. We can optimise for information quality instead of its size.</p> <p>Saint Peter, send me to his colleague, Santa Claus; you remember him? He’s making a list and checking it twice. Gonna find out who’s naughty or nice. And we’ve been naughty, oh we were! So, Santa showed me something from his list to prove it:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/bf71643d590f7b7062d48499f63153cd/a331c/2023-12-15-1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB5UlEQVQoz2VSW08TYRDd//9qYgLKJVWDRojRX+CriYYohaBNgN5297vf26UFjpnpFjA+nEy335yZc2amsnEBGxZodYB2GfNG4vxyhOtpg8msQSMdfp2PcHbxB9JECBNx8fsKP06HHK8nNWLpYEOBshmV8QXSJDTKQ/sFvn3/iaMPJ9h9fYCj98fYO3iDjyefsbs3wP7hWxx/+oLDwTu8eLmDV/sDnJ5dwoQFtE0sqKqlR6sjpM0MKixtgtAR47lEqwxqaTGuJRqh4XzEpNZQrkC7AuWIEyB0YGEVkbUvjwnUhR64qDJIZQEfM7Q2cCGidKs+r/S8zCABNBIuSH9sI4GSuZEjNQI+Jtzf30EZC2U9F6J3ckRzYw7xSSEtxMYlNnEDmgmBLE9rAesjHh6AEDx8iPCp+yefOSTGJlS0NfVMHXUVveVaGNzMW0zrBl13i5zJmuOx/MfRkVG1/Y9tpGJkg2y7kOBCgNIGKRd0qzXGteCc9hlH9nZ5KfS4TaBONB8CWfYhIefEM5wLjVkrMavbzUX0Lnh+vjyqrmgzG+nlqauJTKATobnlUiClhLMGuSz5TFoC52+i4NOJqL4OBYajKa5upjCh8IK2KkmZ9wGr9RrOB0ilcbtas3pahEtLBn2rXvVfqgU11QPPX+oAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Ordering Process Model" title="Ordering Process Model" src="/static/bf71643d590f7b7062d48499f63153cd/a331c/2023-12-15-1.png" srcset="/static/bf71643d590f7b7062d48499f63153cd/36ca5/2023-12-15-1.png 200w, /static/bf71643d590f7b7062d48499f63153cd/a3397/2023-12-15-1.png 400w, /static/bf71643d590f7b7062d48499f63153cd/a331c/2023-12-15-1.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Ordering Process Model, a blast from the past. Don’t you recognise it? Yes, it’s not super-readable. Could I zoom it in? Of course, I could, but would it really help us to understand it? We’d see better what data we keep, but only a little about what’s happening in our system. Finding processes and how they interact and pass information would be, at best, guessing. But no worries, I told you, the end is near, and it will wash away our past mistakes.</p> <p><strong>In Event Sourcing Church, we’re not doing that anymore. We’re not losing business data; we keep them.</strong> We keep them as events. Events are facts about what has happened, and we store them after each operation. And we’re also making a list like Santa, but we call it Event Stream. It’s a list of everything that has happened with our record. It’s a pity, but you cannot change events, as they’re immutable. But you can add a new one in the end and fix your past mistakes. When making decisions, just like Santa, we’re reading and checking a list of events (not always twice; we’re busy, as we’re working more than one day a year).</p> <p>If you join our Order, you’ll find your life more peaceful. We have a clear way of behaving.</p> <p>We start modelling our process by discovering events:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d10f4e2475c456de507f0d712968eeab/a331c/2023-12-15-2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACA0lEQVQoz12T3WoTQRTH99m89SV8BJ/AB/BOQUrBT4pCMRDEelHphb0RKiKokYQ2baKhye7MzvfObjb78Zc5m2yrA8OemeH853fOfzaSJoc0HnFqkKQGqXSwfoNfkym+fP2G2e8lxuczzBYrfB9NcDm/xvj8ChdXC3CZgQkD7TqNsI6E9kiExYobLJnGkimsuMbw3Qc8frKP569eY2//KQbDIxy+HeLN4QDPXhzg+OR0m6MIJoCQ4DLRiLlFIsKGo5tSacFSBWU8lFvD14B0JViqYVwBoTKKuezyAkDCDZiwiEhIeTCZQdscaCoALXajzTjsx4eok0m3bpr+vNxUXdkqo3YlqUXEpCOxULayxY1QU9O30dfgL++hnJ/1+7vr1mUFRkAZuHAUR8J4BGOEKWC0hj87QBVvadoGbduiaVsSCTFdkmu08Qhr7yBsAWFypKENQTBghtqZLqDYEnLvLsrR+38oe7J6Q/Fmegr+6A7y5QTMlJQfekglx9wgTjuXhXIovENTV1vCtp+3R5MJ5NPPyK1CTKVacNGBRUGsc8qQqz3RfyLljyGq8XF31vewRvCAa0/PZluyIacSkUHojFy8TbYT1oP7sEcPblrR1ijWJT0ZguLdjxF9GsX4efEHl/MFCZosh3Ee2nkYl9PaZjmEMpDKbM+7qa2HtMFQT2849PAvCXk8/aY92ogAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="discovering events" title="discovering events" src="/static/d10f4e2475c456de507f0d712968eeab/a331c/2023-12-15-2.png" srcset="/static/d10f4e2475c456de507f0d712968eeab/36ca5/2023-12-15-2.png 200w, /static/d10f4e2475c456de507f0d712968eeab/a3397/2023-12-15-2.png 400w, /static/d10f4e2475c456de507f0d712968eeab/a331c/2023-12-15-2.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Then we find commands, so intentions to run some action. Yes, we’re always having good intentions:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/bf2c0aefebc019f7c1a238c53ae6b10a/a331c/2023-12-15-3.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACCUlEQVQoz3WTa08UMRSG96f5yZ+jib9EvxijIairkDXABz8YY+IFjDdEkKyAQIRlgd2573Tazn3mMe2uGDA2aTpzeuaZ85637UQiJRIaJxC4gSCKFbqo2Ts4Yru/y8/DY/YPTzj4dcLgdMzxcGxjg1MHP1J4oSCWU4Z574Sxxg0Txr5g5MWM3AmOH7P6/hOPuot0n/ZY6C1zf/4Ji70VFntLzM0/5tXrNdxQcm7yA0EQySlw5MaMfMFEaLKiJM1LlE7J8oKirJFpiU5zykyTZzlF1ZDlJUJqlM7QWcHYj3H9GC9M6HihxIsUUudcHU3b0v0c8eX7AfredfL+cxtvm/pPBnVV4U+UbZcbGGAkbemJyv4FNg3rRwmDMx/14QHFaGe2017kVIBv5UpMcZ1QpJhpgC93Fdubm6jlm5QXH9ezWv7+ZHMgOXNjqo0u+ek2kW4IomQKNIaYKWTKi37M1sYGk2c3qMa7M3kNbdtOZbYNVd0wt+ayvjMku3uN5OND/BQcN5pKdoKEkScskKuCDKht/2mFUBlFWZHHHqkU+BNtDbGmOKFk7CcXQFtNO12bGWzvXPJ2X6G/LpCt3rkEr5vWmhLM+tgxB9OYYuz/39gaJCx9i1BvbpOs3LoMrGsr1fFnLr/74dA/HHI0GNqzleiMRKW2YrPK2fNEKLsfJ+rSvomF9pZIC/wNjKk69izk9jEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="finding commands" title="finding commands" src="/static/bf2c0aefebc019f7c1a238c53ae6b10a/a331c/2023-12-15-3.png" srcset="/static/bf2c0aefebc019f7c1a238c53ae6b10a/36ca5/2023-12-15-3.png 200w, /static/bf2c0aefebc019f7c1a238c53ae6b10a/a3397/2023-12-15-3.png 400w, /static/bf2c0aefebc019f7c1a238c53ae6b10a/a331c/2023-12-15-3.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>And we finish our mantra by defining business rules.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b5336166fe4dbfacdcbcf4886fb89812/a331c/2023-12-15-4.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACM0lEQVQoz2WT7U7UQBSG9+K8AH97B16A1+AFSDQaFH8AATYaRROI8gNFBKKu7gpIYFlg4263nZ1upx/TTrvtY9oi+HGSSdOZ6XPe857Thqs0rooYjRX2WDFREZ+/dtj51KJzcMRZf0irfcBx94xur0+vb2E5E9rfjzg6OUdIHy+oGcINaUgvwpY+llAVdGBLFpeazNx/yMLCAquv1pmdW2bp5Vvm5ldYaT7n/YddZh48YvX1Opbwqu/GblADh3a54WOPfSKdMM1zstQABT+GhgtnANkhurVMNuiQAVmWkud5dUfHhqHjYQuFI30atgwYTyJsN8CkKX/Gyt6ErcM2oVhEzt5E78zVB0VxdSc2GY4bVHaVohrli+OGCNfHcjU9kRD39siG+2iTkxiDSWJMpEhNjE7BxMeYsAWxh45CnEmIkAGODGhIFSGVJgg1X3oeT7dGjOZvE765e6UmvxQkfMPsxgWH59v4vcfIezfwO2vIGMZS1cBSplMZGuD6GjdMiUWfTNkU/B06hwMbLHsbZT1Df3uBP+zieDG28OqSR6KuvTQ2Tgz/xk834XSkyPM+08FHON0kMw5JYtdJ0qLyry65BJYdlkHV/iiugUWeX3YRNo8Uzd1TgnANf+MO3pNb19mKKXGSICZhNTaXJatqfkqo/g0simpVCpIcz3cRVpNQtkk8SVFMr89jU82wLeofo7G5P2L/pE/3rI+Okyvgn09I8eQ70tT5L2FiUsZeVPWg9PAXAB41X9zZC1oAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="defining business rules" title="defining business rules" src="/static/b5336166fe4dbfacdcbcf4886fb89812/a331c/2023-12-15-4.png" srcset="/static/b5336166fe4dbfacdcbcf4886fb89812/36ca5/2023-12-15-4.png 200w, /static/b5336166fe4dbfacdcbcf4886fb89812/a3397/2023-12-15-4.png 400w, /static/b5336166fe4dbfacdcbcf4886fb89812/a331c/2023-12-15-4.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Ok, we have a bit more, and we even have a <em>“picture that explains everything”</em> (per <a href="https://www.eventstorming.com/">Alberto Brandolini</a>):</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ff33ce44216f6f88fa985d331b7076c1/a331c/2023-12-15-9.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 42.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAACCklEQVQoz12Sy2oTYRSA5wHc+AwufAn3PoJbly4KIrjxDUQQdNdFpVBEFIu6zEIq2GJVbCypmjZtLk1mMrd/7pOZzMz//59Moi48cDiHw7nzGVoD6D8KKIWuS1A1yAotl/wVXS/RdY4sE2QRIZcpcpnRVAtUWwcYqIYi9llEFt70I874PbHTo0wCYuEQuTaT6T5fT7bx3R6JbxMLmzR0KBOXKnPQZYAuo9UCRpb4jEc288kJ/uA+3tkG1uAFs6FDMu8jzJDnnTtsbBocdLcIvQKlFUXRMBvt8eP7I6xLH8+fs0gFxs2dM269GiLcc6rwLXX4kjw+Ztx5zMG9K8Sft+lN37G7f5vBxR55HJGngrDd3j+nDrpsfzK59uSErS8mxvWdkBu7MYE9Y9obMD76ReyleJ0HTO4aiA8P1w9cFtCkCOuS026fwJqTxTEqStg89Lj6LOfpUYoxNC26/SnuyKb35oDe632c4xGT05/EF4fk3pjQE4zPJwjHYzaYM/wWY1/4uJaFZ1q0b5sGGUnZYNBklKFNZJuI/pCwP8EbDklDl7rIkGVElflE7pwsFMSuiz8RCNNClQGyCJALH3S1OsRQWqNVg5YlShZIWaDqFF2tkZDVgqaIUG1hIWgWgqbwV36dtxrQtBgpSYug8R+FK0e3Q/4FQCu5br7MkXWBrMuVVS2nq0br5Nb+Bm9el4sHGsiMAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="picture that explains everything" title="picture that explains everything" src="/static/ff33ce44216f6f88fa985d331b7076c1/a331c/2023-12-15-9.png" srcset="/static/ff33ce44216f6f88fa985d331b7076c1/36ca5/2023-12-15-9.png 200w, /static/ff33ce44216f6f88fa985d331b7076c1/a3397/2023-12-15-9.png 400w, /static/ff33ce44216f6f88fa985d331b7076c1/a331c/2023-12-15-9.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Sounds a bit simpler than your past mantras, isn’t it?</p> <p><strong>But still, events are critical here.</strong> They tell the story and show us critical parts of the process. They’re building a shared understanding of the process between technical and business people.</p> <p>Yet, time is running out! The storm is here; the end is coming, so let me tell you how you can help yourself get a second life.</p> <p>If you’re deep into the misery of the DDD, so Database-Driven Design, you should not immediately drop all you had. You should embrace the past, do an examination of conscience and move on! What should you especially examine?</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/508a86d4b50037612893263bfe8eed1e/a331c/2023-12-15-5.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACPklEQVQoz02Sy04UQRSGJ66MK92w8QHcuNUXmI1v4cLnMHHh2jUhEaMIEXAM4jUxcjOSEFkQ0AyXQebW01XVVdWX6e5hCM5nunoQOzmp7qq/zvkvXTFxho1zeipk68cO3Z7CE5qeNIjA0vEUra6i2RG0Pcl+o8nvVg9PGPbqDZodn53dX+7MRDkVHaYo26flKSanpnn9ZpnVje8sLX9gcuopL+cWmX4+y8zsAtPPXvD23UdmZuf5svKNhdoyc/M1akvvESqk6FXpyQgRJEiToKMcoROkTujJkK6viZKUJC32I5qewpOWrm+Qpk8Qpggdo2yK0omrSnmQjSslsH2U6bsBYZJz8XgiYPeo5dQENi2xYYYOM7cndeyIVIoPN8H0Kd/LycVqo5T8dEh+do6xFqMDTJQ6vMOYS3xgSiKVi1DMv8rQUUYQZUT9nP2mR70t8QONUsqxNvHAsbHJwOGLe3o8oFIclDIuGRY2FN44NkqhRI+ekNQPG+7y9voqk48f4ktLEJZ4GYwliyB2QVyshXeFR06WTZBK4fuCfDDgdHhGOIL69ia1J49oFx7Gp2VAJkGZoqFr0h+nVfqhndkpNs7whUBKyZ/zc1Ij8FbW2Lv/gE/VKkdzi3ROTlA6/l9y/I9R0VSMqYsxSGuN0ZrRaMQgtqxUq2xeucbW1ets3LnL8cEBQheSI5d0Ze2nYO+ozXGz48KwSV4mGKYugEazTf3wmHw4dL/P1+o9Pt+YYH3iJq9u3UYpi02HTm7h4V8vXx0g2UunYwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="how to" title="how to" src="/static/508a86d4b50037612893263bfe8eed1e/a331c/2023-12-15-5.png" srcset="/static/508a86d4b50037612893263bfe8eed1e/36ca5/2023-12-15-5.png 200w, /static/508a86d4b50037612893263bfe8eed1e/a3397/2023-12-15-5.png 400w, /static/508a86d4b50037612893263bfe8eed1e/a331c/2023-12-15-5.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="1-look-for-status-columns" style="position:relative;"><a href="#1-look-for-status-columns" aria-label="1 look for status columns permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>1. Look for status columns.</h2> <p>Find what values they had; quite often, they may reflect the lifetime phases of your data.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/95cca4483a683599531999b07f06e589/a331c/2023-12-15-6.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByUlEQVQoz0VT147bMBDU//9U8nhAHtIOuDhnXyybaqxikWydygS7dCGwoEjtDmdnyML6EdYPaLVHpz2cH+DiCJ8uWNaV4/o5Yy8kSulg+gEuXKBdQqs8lAnoY8ZQNqGghM4ENMqjUT2HshE+jti2DTQ+5xm//hzw9iHgwsigRICilj3P2sYMWHc9g0kT0Zn4ACam97EsC5TskAIVBgbpdIC06UGmY4yAgkCUG6Bc4pA2chIBvpUNfh4lhImouxbGWCibgTTXDM8aliygoEU+KUL3A7dDe30YICqBY3lCiBF1XaNpamiX86mjXBszGVqbmDV08YqqM3j59h2laGHjFdY5nHcvOPz4Ai12+PtR4v1fCdMn2HCB8SOHvc1kEgNmLSL3f24UaukyWxcgZYfzqUTwHvOycug+66aY2ZMlacgtk/WtDhzcis3UrU9QWqOqa8zzjHVdgG1jwHt+djowAbo+bMp9g34+9HCJ76GxFqKqME0Tu03XKF+zrDkD3Wr0jUhB7ihmlvKJxFhll6WSqITANF0fgGRKo+55Pn8TKZUfRvH1tcXv9xMOxzO7bMPIJ7owYH884XW3Ryv1A5D22YzwDMOvJLKG/wE+NUD6UkrhvQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="status columns" title="status columns" src="/static/95cca4483a683599531999b07f06e589/a331c/2023-12-15-6.png" srcset="/static/95cca4483a683599531999b07f06e589/36ca5/2023-12-15-6.png 200w, /static/95cca4483a683599531999b07f06e589/a3397/2023-12-15-6.png 400w, /static/95cca4483a683599531999b07f06e589/a331c/2023-12-15-6.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>So, an order may be initiated, shipped, paid, etc. They can be transferable to events like <em>Order Initiated</em>, <em>Order Shipped</em>, <em>Order Paid</em>. Of course, be careful and don’t assume that they’re complete. They may be a flattened interpretation of the business process. Always consult that with someone who knows more: best with business.</p> <p>Ah, and remember, in Event Sourcing Church, we don’t sware. We’re not naming events as <em>Order Created</em>, <em>Order Updated</em>, <em>Order Deleted</em>. I told you that you should drop your past sins and start fresh! <a href="/en/state-obsession/">State Obsession</a> is a big sin in our decalogue, so beware!</p> <h2 id="2-check-for-dates" style="position:relative;"><a href="#2-check-for-dates" aria-label="2 check for dates permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>2. Check for dates</h2> <p><strong>Dates columns can tell you a lot about significant occurrences in your process’s lifetime.</strong> That speaks something if they’re significant enough to keep them as dedicated columns.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/dcc64dea7d145e41eb331c3279bb5082/a331c/2023-12-15-7.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByklEQVQoz0VT2W6cQBDk/78qL3mwFEd2YslrbTZmF5ZrLqZngI05KuqePZBKaKC7qK5qMutHWD+g0R6t9nB+gAsjfJywrKvg8jXjT9nh2DmYfoCjCdpFNMpDGUIfEoeyERkXtIZQK49a9QJlA3wYsW0b+PqaZ/zaHfD+t4SjUUhZAKPqerlrGxJh1fZC1pmA1oQ7MSu9XcuyQHUtInEjCUmrCZ2NdzGtcBAyJlFugHIxwQYpcj7iPa/x+tmhNISqbWCMhbKJSEtP6uu4RywjZHzgAlao+0HAz9iXc13jWJxBcUBVVajrCto96lNvSEL4bAIyIyFcUCuH708/UDYaxk9CqIzGuSzhifBxyLH/PMH0EZYmGD8K7PXOIQlh8iLI/EWlxD+2wbgApRSKogARYV5Wge6Tb0qUPVSyhzIyR99oEsgoNkm3PkJpI6PO84x1XYBtE8JbfUqaRACvj4Rye8AvOxPu4fAeaq1Qno74d5kkbV6jtGZReoTo6qG+Csk4HSXKYvqiKA6wvUe5f0a++4lB5cC2skAJhdekuaK+TphG9si+vTX4vT/hkBeSsKVRvugo4mP3hpeXZ5zL412hoyGFQQ8Y+UuCePgfX+lA8HnnTssAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="dates" title="dates" src="/static/dcc64dea7d145e41eb331c3279bb5082/a331c/2023-12-15-7.png" srcset="/static/dcc64dea7d145e41eb331c3279bb5082/36ca5/2023-12-15-7.png 200w, /static/dcc64dea7d145e41eb331c3279bb5082/a3397/2023-12-15-7.png 400w, /static/dcc64dea7d145e41eb331c3279bb5082/a331c/2023-12-15-7.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Of course, <em>CreatedDate</em> and <em>ModifiedDate</em> won’t tell you much, but <em>ShipmentDate</em>, <em>DeliveryDate</em>, and <em>OrderPlacementDate</em> will give you better information. They can tell you both about the business lingo, confirm our assumptions we got from statuses or introduce new events.</p> <p>For instance:</p> <ul> <li><em>ShipmentDate</em> would be a next clue for introducing <em>Order Shipped</em> event,</li> <li><em>OrderPlacementDate</em> can suggest that <em>Order Initiated</em> event may be better named <em>Order Placed</em>, and best trigger discussion with the business if they’re actually the same thing,</li> <li><em>DeliveryDate</em> may mean we should also introduce a new <em>Order Delivered</em> event.</li> </ul> <p>And again, <a href="/en/a_few_words_on_communication/">talk to domain experts</a>; they can help you be a better member of Event Sourcing Church by embracing the truth about the business process.</p> <h2 id="3-analyse-columns-optionality" style="position:relative;"><a href="#3-analyse-columns-optionality" aria-label="3 analyse columns optionality permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>3. Analyse columns optionality</h2> <p>If columns are non-nullable, then it means that you have to be always provided. Nullable that they may be either provided later (so in other operations) or that they’re optional.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b5e5393d1b6370eb3adffd83c7921483/a331c/2023-12-15-8.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB4UlEQVQoz0VT23LiMAzN///S7sM+bmc60HZZegEKgQR8kx3bSWAJ5OxYptQzGtlj6+joSC6s72F9B2E8pPFwvoOLPXx7xOV6ZTudBywrhVI5UNPBhSOMayG0h6aAJmYMbVsU6YGkgIP2OOiGTdsIH3uM44i0zsOAl7cVXj8ruNAzaCKQbK8a9sbGDLiXDYMpipAU78CJ6de6XAZoJRFDA2MDg0gToGx7JyMZI6BIINp1UCmDa9kLE+B8i9fNHs9riSolUgbW+fyGIozrOO4rRrJkAUU6pEzkj3xJvmdLutRCYVtLBE8oP6bYLabQWkG7nkFzbCai05kiiqb9h+W6wsPjEyYvc/x+nGJV7uFjh8Nmju3iL6h+x9vsER/zKQwRbDzdE9ubNzfmRdagQVlL1IJQ1gqVcDCGGHC33cKKNTdmGAFza6JmZt8sk4ZcMgtKuWxFmb5xPYgcZP2JgzQ4BY3L+cgNMk3LGmfz7LkPFHJThImMnC7vergOjfdQ9Qr1/oC+ERivA9IQ5TFrGYSBbjFpbHLJxvP8JIacUaesEWQdNs8/sJz8hK9nwHjluTQuj5a4Ge8TS50/RvFrJvBnscVqs2N9bOiZofORG/E0eUBVLrjcBOhCl5sRvo34l+RK/wOCcT2ijVTSeAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="optional columns" title="optional columns" src="/static/b5e5393d1b6370eb3adffd83c7921483/a331c/2023-12-15-8.png" srcset="/static/b5e5393d1b6370eb3adffd83c7921483/36ca5/2023-12-15-8.png 200w, /static/b5e5393d1b6370eb3adffd83c7921483/a3397/2023-12-15-8.png 400w, /static/b5e5393d1b6370eb3adffd83c7921483/a331c/2023-12-15-8.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>So, in our Ordering Process, if columns are required, then the data we keep them should also be provided in the first <em>Order Initiated</em> event. Remember that a single event type is not always a starting point for the stream; there may be more.</p> <h2 id="4-search-for-tables-with-the-most-1-to-many-relationships" style="position:relative;"><a href="#4-search-for-tables-with-the-most-1-to-many-relationships" aria-label="4 search for tables with the most 1 to many relationships permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>4. Search for tables with the most 1 to Many relationships</h2> <p>At Event Sourcing Church, we believe that setting up boundaries is an essential aspect. To make our processing effective, we group our data around the business process, not the storage normalisation (as our friends from our fellow <a href="/en/strategy_on_migrating_relational_data_to_document_based/">Document-Based Approach</a> convent).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/bf71643d590f7b7062d48499f63153cd/a331c/2023-12-15-1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB5UlEQVQoz2VSW08TYRDd//9qYgLKJVWDRojRX+CriYYohaBNgN5297vf26UFjpnpFjA+nEy335yZc2amsnEBGxZodYB2GfNG4vxyhOtpg8msQSMdfp2PcHbxB9JECBNx8fsKP06HHK8nNWLpYEOBshmV8QXSJDTKQ/sFvn3/iaMPJ9h9fYCj98fYO3iDjyefsbs3wP7hWxx/+oLDwTu8eLmDV/sDnJ5dwoQFtE0sqKqlR6sjpM0MKixtgtAR47lEqwxqaTGuJRqh4XzEpNZQrkC7AuWIEyB0YGEVkbUvjwnUhR64qDJIZQEfM7Q2cCGidKs+r/S8zCABNBIuSH9sI4GSuZEjNQI+Jtzf30EZC2U9F6J3ckRzYw7xSSEtxMYlNnEDmgmBLE9rAesjHh6AEDx8iPCp+yefOSTGJlS0NfVMHXUVveVaGNzMW0zrBl13i5zJmuOx/MfRkVG1/Y9tpGJkg2y7kOBCgNIGKRd0qzXGteCc9hlH9nZ5KfS4TaBONB8CWfYhIefEM5wLjVkrMavbzUX0Lnh+vjyqrmgzG+nlqauJTKATobnlUiClhLMGuSz5TFoC52+i4NOJqL4OBYajKa5upjCh8IK2KkmZ9wGr9RrOB0ilcbtas3pahEtLBn2rXvVfqgU11QPPX+oAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Ordering Process Model" title="Ordering Process Model" src="/static/bf71643d590f7b7062d48499f63153cd/a331c/2023-12-15-1.png" srcset="/static/bf71643d590f7b7062d48499f63153cd/36ca5/2023-12-15-1.png 200w, /static/bf71643d590f7b7062d48499f63153cd/a3397/2023-12-15-1.png 400w, /static/bf71643d590f7b7062d48499f63153cd/a331c/2023-12-15-1.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>To find the stream boundaries, you can start by finding tables that have 1 to Many relationships.</strong> Those that <em>have a lot of ones</em> are candidates for the stream types. We should also logically think if this data can live without each other. For instance, shipment can be a separate process from order, but the order line cannot live without it.</p> <p>Once we start discussing those boundaries, we may find more events and enrich our understanding of the process.</p> <h2 id="5-dont-lie-introduce-explicit-events-for-import" style="position:relative;"><a href="#5-dont-lie-introduce-explicit-events-for-import" aria-label="5 dont lie introduce explicit events for import permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>5. Don’t lie, introduce explicit events for import</h2> <p>Once you distinguish all events you’re fine with and want to migrate your relational data, don’t try to cheat; don’t put your events as small and granular. Relational data is flattened; if you try to retrofit what happened from the final state, you will likely fail or not be precise at best.</p> <p><strong>It’s better to be explicit and provide the <em>Order Imported</em> event with all the current states and the code to interpret it.</strong> It gives us clear information on how we got the data about its lifetime, which can be crucial for troubleshooting and diagnostics.</p> <h2 id="6-experiment-and-validate" style="position:relative;"><a href="#6-experiment-and-validate" aria-label="6 experiment and validate permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>6. Experiment and validate</h2> <p>Don’t be afraid to <a href="/en/prototype_underestimated_design_skill/">prototype and see your model in motion</a>. Our church is forgiving!</p> <p>Try migrations in a safe environment, compare the result with expectations, rinse and repeat. Don’t rush things out. We wouldn’t like to lose old information but improve our future based on it.</p> <p>I hope this guide will help you resurrect information you have in your data.</p> <p><strong>The end is near: The end of flattened data and losing business information. Better to start now.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. You may also like <a href="/en/strategy_on_migrating_relational_data_to_document_based/">General strategy for migrating relational data to document-based</a>.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[A few notes on migrating storage library]]>https://event-driven.io/en/notes_on_migrating_storage_library/https://event-driven.io/en/notes_on_migrating_storage_library/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/69fc49fcfc53a544d63c4b71b936e2c4/a331c/2023-12-07-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADfElEQVQ4y03SzU/TBxzH8R+16AQVhVZGgbZYoAJtoWALpU8wKKOjQMuU0kdCeRAqlgcJXQ0IRZ0ISCCCiyY+THHA4thhO2wuC4nztgvbLrvtsGSXHfYnvDd6WDy8j99XPoevcOZEBjknMzh76iTFeblUFRdRplRgNDZRfl6LqlBJpkiELCuL/ftr/Hxjiu2BAFFPBxliMe8JAumCgEgQEA7Lzc5CLpWgkJymqkSFSaehxWLn6eMv8TicnD6azhFBIDszk6vmataM5UxWKLEpZWSkpyN+FzssTyrlXL4MVZ4Uq15LMhYlFgridTQQdNg5eyLz/6P5/jC/bT/hl51HBJusKUAsEpEmCKlSoLKgELVSgaaogL3P1vnrzXfsLSYJtzrwORpS4PG0tNRKX4uDv9++5s/vd+mwGFNA2rvrDtNrtWhLVYQ6nOxvv+DBgI+56hLCBg2RThf6QhlaWS7HBQGFVMqtgJtkm5W7IQ9DgSCD/l7aHU7yJBIyxSIEk8FInb6SqL+HO5EQL8f7+WHlJjP+S1zuak+BVYX5dFvr2Ir1MWGrxlunY2shzoi3i0Dbh7g/sGLXlWOUSxHMtSaaLfW02swM24389HiTf3494NFsgiverhTY1WhlMz7K7zMjHFwJkPS62F2/ha/ZisNYif2CFkOpCktxPoLFVE9DrQG7sYaBJjOvZq7xbH6O6MVOroV9WMpKcNbVsJkY4+D+AvtTg8zW61gfH+BikxVzRQmu2kra9RUMNBoRDHo9XreHewsLvNl6zqt4lMXBELGgn0/6QtQpCzCoVXy9scwfXzxgd2qYle42Ek4rbQYNhlIlGy4bS7ZqAqZKhC73x+w83eHbz7d4+9Ue+zvbPPs0ye7qMg9vL2JVyJi0VfN6fowf+9wstZiIuZsZqikhWqXCdi6fyRo1d8068o8eQYhFJ5mPz3B7coIXK8u8vLfC6vQET27eYD1xHU9lGavOemaN5Vy/oGbR107QbsB1Xk6fuoARnYruolz8Sgllx8QI/u4Ql3sjjIXDrM/NsZaIk4wOsjEzzZ2xUTyaUoZMWsIWPWONRpI9Lh6ORlgbjtCiVnBJIWWwQknr+6coEgkITXYH4W4/0d5e5sbHSfT3Md0bYGkqxtLEVdo0pVhzjtEsz6FHnsu0Usbzzha+mY8zHwljyMumXS7hI9kZZP899r+/5ujFM4CqegAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/69fc49fcfc53a544d63c4b71b936e2c4/a331c/2023-12-07-cover.png" srcset="/static/69fc49fcfc53a544d63c4b71b936e2c4/36ca5/2023-12-07-cover.png 200w, /static/69fc49fcfc53a544d63c4b71b936e2c4/a3397/2023-12-07-cover.png 400w, /static/69fc49fcfc53a544d63c4b71b936e2c4/a331c/2023-12-07-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I’m feeling like a surgeon in recent days.</strong> Who knew that changing the connection management in storage library would be a delicate thing to do? I did, but it’s still an intriguing feeling.</p> <p>Most people say: <em>“you won’t gonna change database ever!”</em> but other things might happen that bring almost the same complexity. Examples?</p> <p><strong>We created <a href="https://github.com/JasperFx/weasel">Weasel</a> some time ago, a spin-off that maintains the schema for <a href="https://martendb.io/">Marten</a> and <a href="https://wolverine.netlify.app/">Wolverine</a>.</strong> Initially, we wanted to offload the Marten codebase and reduce coupling. We also hoped that it may be a first step to support other databases in the future.</p> <p>And that happened to some degree. Wolverine supports not only Postgres but also MSSQL. What’s more, it’s supporting not only Marten but also EntityFramework.</p> <p><strong>Adding a new database or storage library to an existing, big codebase is similar in complexity to switching a database.</strong> And that may also happen to your business application.</p> <p>Even if you don’t, then libraries evolve and change conventions. For example, <a href="https://www.npgsql.org/">Npgsql</a> (the PostgreSQL provider in a .NET world) changed conventions on connection management. Initially, it was a static configuration; now, you can set up individual <a href="https://www.npgsql.org/doc/basic-usage.html#data-source">data sources</a> with different configurations.</p> <p>And that’s a change for good. That’s the right direction the Npgsql team went, but making that switch is delicate. <a href="https://github.com/JasperFx/weasel/pull/112">And that’s what I’m doing right now.</a> Thus, surgeon-like feeling.</p> <p>Of course, we could ignore and use the old way until it’s fully obsolete. But then we’d need to change much more as our code will grow in that time, and we’d need to do it in a rush.</p> <p>Also, we’d get much stronger pressure from our users, as the new features may be only available in the <em>new way</em>. And that’s already happening, as to benefit from the new <a href="https://learn.microsoft.com/en-us/dotnet/aspire/database/postgresql-component">Microsoft tool Aspire</a>, we need to use the new <em>data source way</em>.</p> <p>We could just switch classes and call it a day. That still wouldn’t be simple, but it would be at least faster. Yet, that’d be a half-assed solution, as we’d not benefit from the new features. Plus, trying to put your legs on different sides of the canyon is a recipe for disaster. Usually, the library creators assume that you go either one way or another, so choosing both can push you to nasty edge cases. Those are those that you usually find after production deployment.</p> <p><strong>You may think that <em>meh, not my problem</em>. You may be right, but this may also happen to you.</strong> Even if you use ORM, it may change its conventions and become obsolete; others will supersede that.</p> <p>Should you then wrap your tooling? Nope, because you will end with the lowest common denominator and neglect the change.</p> <p>As always <a href="/en/the_magic_is_that_there_is_no_magic/">the magic is that there’s no magic</a>, the only solution is cautious evaluation and managing your dependencies. Keep them up to date, observe trends and predict how they may impact your solution. Plan what you do about them, and define strategy and tactics.</p> <p>Don’t assume that <em>it won’t ever happen</em> as those are famous last words.</p> <p><strong>And hey, if you’re feeling from time to time as a surgeon doing an operation on an open heart, that’s fine.</strong></p> <p><a href="https://www.youtube.com/watch?v=M0SjU95U3-k">Somebody’s Gotta Do It</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Are you Marten or Wolverine user? Tell us more!]]>https://event-driven.io/en/marten_user_survey/https://event-driven.io/en/marten_user_survey/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/31cc9f8c5bb38e33d52f423a0c236a58/a331c/2023-11-30-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADoUlEQVQ4yzWSWVDUdQDHf5TJkSzIsa3LBnEtyymwCsTGIccCsrbcmSiiqCDLITAQYPaiib7QJIMIBZZO2YhBohmGMIA1AkNYkw9R1Ayko5Nl6Qxy7H/5NNj0ffm8fWY+M19xU5IYkMz0SWY+M5s5D1wGri6bGV4y8+vSEvOSmRWLhf+3sLLCTxYzXzyap2nsLm/3/4apa4o9zSOICcnCmFliZFVgkegYH+W98W+ZlCQeWKRnAov0HzvbTlFelM/M06eMrkD77GOOTjzk8MAsdX0zlHZNIW5LZm4vLzMDTM79zKGUUA4YYpheXGJheYkLH7Xzz5MnLC7M03wwixPlhdwHLvxwh/15Rna8+zGVV+9T1fsLlZemEY/MEn+smPnwXCsV5UWYksI4VprP9Nws3418RVvjQSanJqmqKKZ6bw4n3qnnT6B74BqNoa4k7zTx1tgi9YP3aPnxIWI15dat6yTrgonRqknbsonJkSEOx8dQo4+ivbmJ2tpq8vKMHDl5klC1J71zv1PXdoamyr2kG19Hm2Tk9J05bixJiL/+fkB1xW504RoMiZFoA1T09naTExyEKSmC1EA13go5RYX5ZGUYSQn2pa/7HAcaGun4vIdNGh+eE4Lihnq+B8ToyDUyUnXowgOIDlOj9Vdxc3iIAm04akc76g+ZKMvQs9OYzqthoWz282Vw8GuMmdmYamqQ29rgbGuDh2ID1+/eQ3R+8P6zzIiNGpQya9LidFy88iUquQqlgwvdPZdIjdEhEwI3+3UEKjawJSKclxVuFJRV42ptg0K2jiBvT0obGhAJ8bFsC/clxEOJjRCEaXzZta+Yjd6+ZKfqiQwKRGZlhZdczhohKCksYHjoBpmRoSRG63hRCAzZWdQ1HScyNg7h5x+CIcgbrcoN2Qsy9LrN5CXpOFZTQmFKNH5KBRd7u9menYMQgtqqSrrOdhLs6oyT1fOsFYLjp9vpH+xnV34uQu0VgML+JZQO65FZ2WHMyGX3G9n0tB6hITOMltZTrN56a1Iyrna2qL39sF8j47V4PanpBpztnEjPzKVvapxPvhlGqJzdUTgoyDTo8XBR4mjrQoCfhv3bIqnOisKQHEdDfRnaQA3+Xp64u3rwZn4OHZc/paSuFo2vD/7+gZyZGOfK4wWE41onwjUhnD/bgj4hHrmDOz5u7mjcnAjxkpMYG8XWxCg0ryhxtLZFuV5FelIk27P17Nm3g7S0BNyV7uQaEzja0cq/rSVgBw6n3zQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/31cc9f8c5bb38e33d52f423a0c236a58/a331c/2023-11-30-cover.png" srcset="/static/31cc9f8c5bb38e33d52f423a0c236a58/36ca5/2023-11-30-cover.png 200w, /static/31cc9f8c5bb38e33d52f423a0c236a58/a3397/2023-11-30-cover.png 400w, /static/31cc9f8c5bb38e33d52f423a0c236a58/a331c/2023-11-30-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I’m always saying that the Marten community is one of the things that keeps me doing Open Source and staying in the .NET community.</strong> It’s really welcoming and supportive. Of course, there are distinctions to the rule, but they’re rare enough.</p> <p>At the time of writing, we have almost 1000 users on our Discord. It’s a nice place to be, not only for bug reports but also for discussions around architecture and software design. If you’re not yet there, <a href="https://discord.gg/w5wGvDpy7E">come and join us!</a>.</p> <p><strong>So we’re trying to stay close to our users, and I think we’re doing a decent job on that, but there’s something we could do better</strong>: understanding the specific use cases, being more proactive, and learning what they precisely do. Also, getting more case studies on the domain products they built.</p> <p><strong>That should help to:</strong></p> <ul> <li>keep closer human-to-human relationships with our users,</li> <li>building features more suitable for their needs,</li> <li>providing better services, also <a href="https://www.jasperfx.net/support-plans/">paid ones</a> to help them deliver business value faster with better resiliency.</li> <li>showing other new people what can be done with Marten and who uses it.</li> </ul> <p>So, if you’re our user, I have a small request: <strong>could you fill out this short survey?</strong></p> <div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 65%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <iframe src="https://docs.google.com/forms/d/e/1FAIpQLSddHF-GzTldtsGUCzbY6tjsl842rMyorDLxdLcnIllKcAmLTQ/viewform?embedded=true" frameborder="0" marginheight="0" marginwidth="0" style=" position: absolute; top: 0; left: 0; width: 100%; height: 100%; ">Loading</iframe> </div> <p>Thank you in advance!</p> <p>We’d really appreciate that. I genuinely believe it should help you and us get even better synergy.</p> <p><strong>We’re also planning to reach out to the people who filled out the survey to get on the video call and learn from each other even better.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to deal with privacy and GDPR in Event-Driven systems]]>https://event-driven.io/en/gdpr_in_event_driven_architecture/https://event-driven.io/en/gdpr_in_event_driven_architecture/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b04e350d77a67f8c494264f5eae83e2e/536f2/2023-11-26-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACaElEQVQozy3LOU8UUQAA4EnstbCSaGe0QC1IjGiCHYUNCYX/QHsrY2Kh1iYUeMQIJCQkgMhhuMIGgV3uLLCH4+w195s3x7vmzbnsLirGxP77BB41WZAiFpvQk6r15dVMpa5+38wurWR29vP5k/JWbi+3eyhWGkdFsSzWMQ15lPpBimgoUJ54NMR+ohp2oSytZjbmFhanZhbeDg1/nV+c+7Y8O780OT07MTnzeWz82/KaR0NEo//ZxQGiEfYTwlPPo4QElscsh4Zph8dtP25RnvKo5cftsHkWJG3oMeBQ6FJEQgH7MeEJ8WPsxx50TVmTxEpF1TCP/KhFeYJZjFiE2T/QUEGhLB0VxeOiqJmOgFhEg5QEp0HSITyhQYr9UNZVWbd0y/NIwIImC5qEJ4hG1Ya+vZff2t4/Ovkhq0DgcctDePjN87Gx8bj1G/PYxrxRV6WaKmuWCZEJEXAI4TELUmAjsdJYzWxINUUHjkCjTmF383HfrTs3rxfLFUQjoOnYsgiEtoMdAB1Nc3QDWC7hqQlRNrsDbGI5FHpMIEGrLhb7e2486u8PovT07I+hmdAEpm6YlgdV3ZEVV1YgcGn8q5Tb+Ph0cGJk5PTs3EG+QHgaxK3RD8MDA4MvXr7KrGcNYJfKUl3RIfIVxfwpVmtVGVgeDjv2wbo09Gzty1TUOncxE0wbH+RL795/ut/7QBAudF25WisWEuS6mgpdBjWdWYCaBjBML2zDw83c6ye1con6iYu5MJ8Te+729d572N19u6vr2qWLlxeX1ogfGpbDwtT2CIAusF3sR1CRxZVpaX7UON61FQWx+C/EXhxGZPaVkAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b04e350d77a67f8c494264f5eae83e2e/a331c/2023-11-26-cover.png" srcset="/static/b04e350d77a67f8c494264f5eae83e2e/36ca5/2023-11-26-cover.png 200w, /static/b04e350d77a67f8c494264f5eae83e2e/a3397/2023-11-26-cover.png 400w, /static/b04e350d77a67f8c494264f5eae83e2e/a331c/2023-11-26-cover.png 800w, /static/b04e350d77a67f8c494264f5eae83e2e/8537d/2023-11-26-cover.png 1200w, /static/b04e350d77a67f8c494264f5eae83e2e/536f2/2023-11-26-cover.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>The law to be forgotten and immutable data sounds like fire and water.</strong> How to remove data if you’re building an event-driven system based on append-only, immutable log. Is that even possible? Let’s find out!</p> <p><strong>Yet, before we go, a reminder!</strong> <a href="/en/gdpr_for_busy_developers/">In the previous article, I described the general considerations and strategies around handling privacy regulations like GDPR</a>. I encourage you to read it, as I assume that you understand that data removal is just the tip of the iceberg. Before choosing tactical moves, we should ensure we have the right data governance strategy. We need to know:</p> <ul> <li>what data we store,</li> <li>why we need them,</li> <li>how we’re processing,</li> <li>where we keep it,</li> <li>how long we keep them (including backups).</li> </ul> <p>Still, let’s dive into the privacy specifics of the event-driven systems!</p> <h2 id="data-segregation" style="position:relative;"><a href="#data-segregation" aria-label="data segregation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Data segregation</h2> <p><strong>Data governance practices are, unfortunately, not discussed enough in event-driven systems.</strong> People treat events more like freehand drawing, sometimes as mesh, but ending too often with a mess instead. We should carefully draw boundaries between our events.</p> <p>Such practices can be more technical, like:</p> <ul> <li>streams in Event Sourcing are consistency guarantees where we can get guarantees of atomic writes, <a href="/en/optimistic_concurrency_for_pessimistic_times/">optimistic concurrency</a>, etc.</li> <li>partitions in Event Streaming give us control over ordering guarantees and parallelism.</li> </ul> <p>Still, technical aspects are just part of it. We should also think about the logical split. That’s even more important.</p> <p><strong>We can group streams into categories (e.g. all user events) and partitions into topics (e.g. all events from a particular module).</strong> The proper key definition strategy can represent this. Just like we don’t put all application data into a single table/collection, we should not put all our events in one random place but think about how to group them logically to make our system efficient.</p> <p><strong>Besides the traditional split, we should also avoid keeping personal data with regular ones.</strong> As you’ll see later in the article, that makes handling privacy rules much more manageable. But even with common sense, it’s clear that keeping personal data segregated gives more options to apply security and privacy regulations described in <a href="/en/gdpr_for_busy_developers/">the previous article</a>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/7ec99052f394d52373ba6d52eeda11cf/536f2/2023-11-23-categories.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHklEQVQozz2QW0/bMACF8/+fJ02T2H2IgbhoQmOjaqEbFddSQksb2qaOYzuJ47sdpy1DmrbsYdLReft0LoFyS1Otua6n8yQGsNU+DUdRNF1c9cOj4863Vuf+4bF30W+f/ux0z0CaT+dAGq/dspQ2kKa+GQwhLu8fHm/D4db23pt3n15tvN/ZP/ywub21c7C5vff67cfNz7svXm786F2GwzGhUtklky5IM77/5WgGiPHPpfLa/9KmElJKpZW22jipjTFWaaO0EaZm2hfclcJSboJSugTTkmtlrFCWmzrPCU5nBCcEA4JTggCCC4wAQZArJ+2Sa89V1cBM18rWs+nk6uZ2Mh6FoygllOY4w0mGYYZhniGhPTdLpjxTnkrHlC+Fa2DlVqWwAIDpbAYSMIMoTjFI0wTBpHEEMcIFQ5THKYpiME/RdIFIIZrNVDimHNe1NHUhTEzQguBFRv4rJjgmCOTZYDQ+7pxeDe7Or+8gplTYgMqqlBUVlkrHVcV1LUzNlefa41LMMVoQArK84DZjmpvK+HX99FvZVVO7AZQvZcWUF+YfaVyhdC4k4TwpcsRKpiuuammWoyjuh+PhZJ5TyZQLCm6psKV0TTh3XPkYkvOb20m8GDyMw0l0P44gYSlhXPtoBncPDnuX/YLpgpvgbMIikEGUa7vU1UpX6zjBh19bvYvrVrvbane/t066ZxfTGNr6ya+eCW2uMm79d/MfCi5DKHB/DoEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="categories" title="categories" src="/static/7ec99052f394d52373ba6d52eeda11cf/a331c/2023-11-23-categories.png" srcset="/static/7ec99052f394d52373ba6d52eeda11cf/36ca5/2023-11-23-categories.png 200w, /static/7ec99052f394d52373ba6d52eeda11cf/a3397/2023-11-23-categories.png 400w, /static/7ec99052f394d52373ba6d52eeda11cf/a331c/2023-11-23-categories.png 800w, /static/7ec99052f394d52373ba6d52eeda11cf/8537d/2023-11-23-categories.png 1200w, /static/7ec99052f394d52373ba6d52eeda11cf/536f2/2023-11-23-categories.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We can break it down by categories/topics:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6cb3df19cafe66a0f4de290b624bb525/536f2/2023-11-23-segregation-categories.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACJUlEQVQozyWSW08aUQCE9/8/NvGhaaMhoZFIrU00KCIVLSLWICKyyy6cvbG3c7/uAu1Ls/FxkplM5stYXFVUGMT0chWsQXjZ7U9e3+cL9/Fpct657lz1prPFYDi+vhl0+wMQZd7Kp8JwWUEiLcJNjgViZjpbvLzOW+2zo0bzy2Hj+9l587jdap8dn5weNprfWu2Dz18Hw/Hr22KTEybKOpxBAakmoqKiQswIs5dmz2RFZYWY5mpHRSX0npu/svz34SmIgkQWWFi4FjKFFHNDRJnkJIizIM7CTe54fhClQZT4YRLEGSSKiIqIEnODqcqxsITejZ+no6cXpraYm01WhMCJgRP6K8ddgzBahz6IIhDGORYFUSlkiGlEdd2syv30zZ7ObWF2TO/TNI0fWuFtI5l0XADc+dC7a7qj07XvMrVLMtzp9m9u7x3Xx9xYBZaYGcQMpBKxMo4T0D9adQ7Chx8Lb20/9+yLT3bv0PUWOZZcbR03GD/PEFE54lZBdEFUQSQkEvNyk+Srx3Pv/sR/6b87nv02mt8dz0c/l6tlhmVBFGKaiJIwXRBpIaoR05BqSBXmJSQqRSzHPM6QB+I4JzHkG8iSghZYQlqvRdzUYSytDNXQa/R1v8LMRBs4mdkgyhZL4HiBvfRBmAYbiKiCVH3YIJY5EtZvGzkgCeKUypKriqvtyo8vLrvDxz/X/UHvZnDV+3X/8OSCqCaqKq639Qu4gVj+B4QzPzYn4mWRAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="segregation by topics" title="segregation by topics" src="/static/6cb3df19cafe66a0f4de290b624bb525/a331c/2023-11-23-segregation-categories.png" srcset="/static/6cb3df19cafe66a0f4de290b624bb525/36ca5/2023-11-23-segregation-categories.png 200w, /static/6cb3df19cafe66a0f4de290b624bb525/a3397/2023-11-23-segregation-categories.png 400w, /static/6cb3df19cafe66a0f4de290b624bb525/a331c/2023-11-23-segregation-categories.png 800w, /static/6cb3df19cafe66a0f4de290b624bb525/8537d/2023-11-23-segregation-categories.png 1200w, /static/6cb3df19cafe66a0f4de290b624bb525/536f2/2023-11-23-segregation-categories.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Or even with separate clusters:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/85d78af4c243176d8b08e13666c50ddc/536f2/2023-11-23-segregation-clusters.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHklEQVQozzWR2U8aYRTF5/9/b2JrFxMXUIlNtUFEURZbaI0FwWEbGIZlvvn2/Rtc+tAMbV9ucpPfyT3nXE/olEmHuRkFs8k0KpYrvx4ee/1R88f9WbFcLF22u37ttlWuVMtX1XAOxkHIpBUqRVR5VFhIFOa2/eDft7v545NPO3vbH3YKn892D45yRye5w8LHnd39/PGbre3qbavT9ZcJZdJlYoAkYobKlMkUi1S6F+VehH3l5olIp+yrNK/a/ZY2m1ymmFtINaIKEumRbFEx4kRYkiySaAjmI7iYYCaXCcfcTMFyOJ+OluFkNcfcMJlmJNMJkZ40z8279vefbaZc1DwJ6wezRj6s7QEQr5ACRAxaX3r1/GMt17stLGOYYIWZJsxkl7V7bnf9Tm8gtAtruXH5/fji/bj0DiMAmcVCjas5v/S2X9waXu+GUVS6vLm6rg9GIRHWg0QRbrMkRExbp5PG4aRxGNT2YxAvEwGo8Jtn3cbRYy3fqRcQZcNg3rrrYKoTLLxN+qwAxDQkAiQQgARAAomKVhhSvUB8lpApQBGkkCrMDVWOcgOp8jAzmBvEDGKaCEtlSqSjKqs9RoKptTDPXK0Js3jDbHj7TwywTIjMqv9vATMdQ37/0B8EkT8Mu/0gWuENkLlDVP99VYKl1+jjwXQ1W8RMOWHWVDihn8J5fPr1vHLTuKl/O7+oBOFCuxeuUmHWwqy5TpmwiKg/+B8/lqVAxHQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="segregation by clusters" title="segregation by clusters" src="/static/85d78af4c243176d8b08e13666c50ddc/a331c/2023-11-23-segregation-clusters.png" srcset="/static/85d78af4c243176d8b08e13666c50ddc/36ca5/2023-11-23-segregation-clusters.png 200w, /static/85d78af4c243176d8b08e13666c50ddc/a3397/2023-11-23-segregation-clusters.png 400w, /static/85d78af4c243176d8b08e13666c50ddc/a331c/2023-11-23-segregation-clusters.png 800w, /static/85d78af4c243176d8b08e13666c50ddc/8537d/2023-11-23-segregation-clusters.png 1200w, /static/85d78af4c243176d8b08e13666c50ddc/536f2/2023-11-23-segregation-clusters.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Segregation is not the only thing we should consider; let’s go and discuss other things we need to consider.</p> <h2 id="retention-policy" style="position:relative;"><a href="#retention-policy" aria-label="retention policy permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Retention Policy</h2> <p><strong>Defining a data retention policy is crucial for handling privacy regulations correctly.</strong> The same is true for event-driven solutions. In general, we have two types of tooling to consider:</p> <ul> <li>Messaging and Event Streaming tools such as RabbitMQ, Kafka, Pulsar, SQS, Azure Service Bus, Google Pub Sub, etc.</li> <li>Event Sourcing tools (event stores) like Marten, EventStoreDB, and Axon.</li> </ul> <p><strong>It’s worth pointing out that <a href="/en/event_streaming_is_not_event_sourcing/">Event Sourcing is not the same as Event Streaming</a>.</strong> Event Sourcing is about capturing and making decisions based on events; Event Streaming is about moving those events from one place to another, so integration. Kafka and Pulsar are not event stores, Marten, EventStoreDB are such.</p> <p><strong>Even if tools like Kafka, Pulsar, etc. can keep data with an infinite retention policy, it was added for resiliency, not to be used as a database.</strong> Event stores have messaging capabilities, to forward recorded events, but they’re made to keep data durable, not to get high throughput on message passing and services integrations.</p> <p>Still, we should define retention policy and archive strategy for both cases, but with a bit different considerations.</p> <p><strong>Let’s start with messaging and event streaming solutions. Things are a bit easier for them.</strong> If we assume that their main goal is to move data from one place to another, and storage is an additional capability, then we can conclude that not keeping them forever is more than fine. And it is, as the fact that we can do something doesn’t mean we should. Keeping data forever will make our costs higher. Also, if we’re using managed services like Azure Event Hubs for Kafka or clones like Kinesis, they might not even allow us to do so.</p> <p>Each privacy regulation is different, but all have rules about how long we can keep data after a request for removal from the user. For GDPR, it’s 30 days. So, we should be good if we set up a retention policy below that. Of course, as long as we respect the request and won’t publish new messages containing Personal Identifiable Information (<em>PII data</em>). Such a retention policy will effectively make those messages unavailable (even if the user didn’t ask us to do so).</p> <p>Depending on the tool, you can define retention policies for:</p> <ul> <li>the whole cluster (e.g. <a href="https://kafka.apache.org/documentation/#brokerconfigs_log.retention.ms">Kafka</a>)</li> <li>topic or stream level (e.g. <a href="https://docs.confluent.io/platform/current/installation/configuration/topic-configs.html#delete-retention-ms">Kafka</a>, <a href="https://www.rabbitmq.com/streams.html#retention">RabbitMQ Streams</a>,</li> <li>queue (e.g. <a href="https://www.rabbitmq.com/ttl.html#message-ttl-using-policy">RabbitMQ</a>),</li> <li>message time to live (e.g. <a href="https://www.rabbitmq.com/ttl.html#per-message-ttl-in-publishers">RabbitMQ</a>).</li> </ul> <p><strong>For event stores and Event Sourcing, things are a bit harder.</strong> Event stores are databases, and we’re using them not only to publish them and integrate different parts but also as our state. We read them each time to make our business decisions. Some event stores allow defining retention policies. For instance, <a href="https://developers.eventstore.com/server/v23.10/streams.html#reserved-names">EventStoreDB allows us to define the max age for events</a>. Still, we need to write our logic, always thinking that those events may disappear. It is not perfect, as it’s non-deterministic, and if those data disappear, then our logic may crash. It’s better to model lifetime explicitly and think about our archive strategy. But we’ll get there.</p> <p>Rarely do event-driven solutions provide the option to remove specific messages. If we’re using an event store that is built on top of another database type (e.g. Marten on top of PostgreSQL), then technically, we can remove data, but it’s not recommended, as it may not be effective. It’s hard to know if someone has already consumed it. I wrote about it longer in <a href="/en/what_texting_ex_has_to_do_with_event_driven_design/">What texting your Ex has to do with Event-Driven Design?</a>.</p> <p>Also, as <a href="/en/gdpr_for_busy_developers/#the-law-to-be-forgotten">explained in the previous article</a>, the fact that we delete something doesn’t have to mean that it’s actually erased. It may just mean that we can’t access removed data easily.</p> <p>Let’s discuss how we can remove it from event-driven tooling.</p> <h2 id="log-compaction" style="position:relative;"><a href="#log-compaction" aria-label="log compaction permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Log compaction</h2> <p>If you’re old enough, then you should recognise that picture:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6f107f91755dee213cae0cacf8004072/536f2/2023-11-26-defragmentation.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACJ0lEQVQozy2Sy08TYRTF55907caFW1cmRBeGsFBkYWJcSGIwCorgIzaCJIYgNa3FogQIBaUUWuh88z3v/V7z7Ixp9ebkLs7m/JJzAoVegAspAGjnYqljAU6CjTiGDKm0Eh1HL9FToUMGlCNoL9FSaQOhbMgQ0Ky3Lz81TiglQiFjQgjgUoURVYBKG2N9mibWx+ehigQKZZi0QcjgimKRJVOvjqbfHFRVmeWjsqrysipHZTmqqqoqyyrLR2lWpElxTjDkSBhQoQMmLZE2T5PZj907i20uur2zE3J6TDud037nT2/v8OznYae1397aa6zttNa7QybBRxwjoQMqbch14v2TjcH1ueXlzZlH63fnV24vLUwtrNxbWJ1+uXr/cW1m8f3cu9cP5mvTnfOB1AkVmgoTKB1z8Hmavtge3nj4od5cWmuufN1a/bxba2/Xdlob3/Y3mwf11nGz3fn+46DRCwWYhElDpQkI1yFH7/zidv/Ws90YFBlSg7ZI8iwr8rwYFaOyKMuizPMiSbILglQYwoBwHUTCDBk6a5fq/ZtPd1OPhFLnnfcujn0cx4SEUklAQIQky/oRUmnYP2wqLeHaOfe2Mbg220gdEhJpM3bSyRlrjbXWufHz8SACDu4/dsiQcC3B/Dqlz7/8BtBcaaE0HfesCYOrSIYUJt0gYTikY+AJNgb1o+i4e9m7uNI2TtKMKceUE+iljuVkWBK9mEhqr3QME3+cLMxfAvxK1CyO5VoAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="defragmentation" title="defragmentation" src="/static/6f107f91755dee213cae0cacf8004072/a331c/2023-11-26-defragmentation.png" srcset="/static/6f107f91755dee213cae0cacf8004072/36ca5/2023-11-26-defragmentation.png 200w, /static/6f107f91755dee213cae0cacf8004072/a3397/2023-11-26-defragmentation.png 400w, /static/6f107f91755dee213cae0cacf8004072/a331c/2023-11-26-defragmentation.png 800w, /static/6f107f91755dee213cae0cacf8004072/8537d/2023-11-26-defragmentation.png 1200w, /static/6f107f91755dee213cae0cacf8004072/536f2/2023-11-26-defragmentation.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>It’s a print screen from Windows XP that shows the <a href="https://en.wikipedia.org/wiki/Defragmentation">defragmentation process</a>. It was an important process in the days when we had magnetic disks. At some point, our disk would be filled with some small empty spaces. You could put the data there, but it would distribute the file all over the disk. It would slow processing as magnetic disks spun around to read the specific data. The more it was spinning, the slower it got. To limit this effect, there was a process called defragmentation. Windows could take the files and redistribute them, sort them out, and lay them one after another so that we would have bytes one after another without empty spaces. This process can also remove dangling bytes that are unused by any application.</p> <p>You may be thinking now: <em>“OK, boomer”</em>, but bear with me. I’m telling you about that for a reason. You can use a similar technique to clean up data in the event-driven tooling.</p> <p>This technique usually is called <em>log compaction</em> (<a href="https://docs.confluent.io/kafka/design/log_compaction.html">per Kafka terminology</a>). It has also different names, like <a href="https://developers.eventstore.com/server/v23.10/operations.html#scavenging"><em>scavenging</em> in EventStoreDB</a> or <a href="https://pulsar.apache.org/docs/3.1.x/concepts-topic-compaction/"><em>topic compaction</em> in Pulsar</a>.</p> <p>Log compaction is a process that takes a sequence of events, keeps the last event and removes the rest of them. Log compaction will perform such transformation for each of the sequences of events grouped by key and ordered by position in the stream (so in order of appearance, read more in <a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>). In Event Sourcing, one stream represents one record/process; in Event Streaming, the breakdown can be less granular (e.g. a single module or other message sources).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/775b359eed6c5d97432efde8cb3c7156/b58f7/2023-11-26-log-compaction.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB+ElEQVQozz1Qa2/TMBTN//8BCAkJiQ8ICW2wqS1dWzYorGxMjJfaAWvnJEtSx7Edx684TtoEOUMcHV3dD+fo3Hu8Qhpa6CxX91t08+v2xcvDL99+jsbTg1fHR4Pxp8/Xg9HJ6OTtxdXX+fnlh8VVigsmSiZKUmiPFDolIstVEKPV7838/HI4nh0PJ0+fPR+8mU7PPg7Hs/Hk9PBoePB6OBjPYsQQEYQpkisvxRxRSZgmfb4s66reS22rem/sXpm633dN25m6lWZHmAtDRGAqvQfbPzLNhUqSBADg9wiCYLPZJHFsytLaBlFBexnOZUaFh5nGTP2nVCVCKIri7RZCCLcQRj3CMOTCHdjLNGHKJTNhHKVhqsp5WdnGWsu5oJRCCHGPqqq6rrN146qSTk8LjanyUuoeyKjMcpESUVa26zouBABguVyu1+u2bbsela0RFYhKSHiKC0SEl+Wqp8T9tPXOGBNFEQAgCALf94HvK6Vdsm0g5u61shG6Jkx7zkNlSkWWyyyXlW32u53WmnNe9NBaP5y9b7ubW/Do8ZPT94vJ7F0YIy9CHJLeSSWirkPChB8md0G0AcEahFGcRgnKaJFzfRduz+aLH6vb6++r+zjzLv6Q1Sb2wygXunCdGcxUktIEOcYpiSHeovyhV65tWbfa7qXZ0UL/BcdlVTPL+sY1AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="log compaction" title="log compaction" src="/static/775b359eed6c5d97432efde8cb3c7156/a331c/2023-11-26-log-compaction.png" srcset="/static/775b359eed6c5d97432efde8cb3c7156/36ca5/2023-11-26-log-compaction.png 200w, /static/775b359eed6c5d97432efde8cb3c7156/a3397/2023-11-26-log-compaction.png 400w, /static/775b359eed6c5d97432efde8cb3c7156/a331c/2023-11-26-log-compaction.png 800w, /static/775b359eed6c5d97432efde8cb3c7156/8537d/2023-11-26-log-compaction.png 1200w, /static/775b359eed6c5d97432efde8cb3c7156/b58f7/2023-11-26-log-compaction.png 1324w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>You already know that <a href="/en/event_streaming_is_not_event_sourcing/">Event Sourcing is not the same as Event Streaming</a>. Kafka and Pulsar are not event stores, Marten, EventStoreDB are such. Still, internally, they are usually built as append-only logs, so the log compaction part will logically work the same way.</p> <p>In Event Streaming solutions like Kafka and Pulsar, log compaction will always keep the last event in sequence.</p> <p><strong>That’s important to remember, as we need to ensure that the last event won’t contain any PII data. We usually put there so-called <em>“Tombstone Event”</em>.</strong> It’s named like that, as we’re putting it on top, marking that this stream won’t be ever used again. It may be modelled as:</p> <ul> <li>a dry event like <em>PersonalInformationRemovalRequested</em> that has just the bare minimum of information like user identifier, requested date, etc.</li> <li>a summary event with user information that is not PII,</li> <li>a summary event with anonymised PII information.</li> </ul> <p><strong>In event stores, that works similarly, but we may get more options.</strong> E.g. <a href="https://developers.eventstore.com/server/v23.10/streams.html#hard-delete">EventStoreDB gives functionality to <em>hard delete</em> event stream</a>. Technically, it puts the tombstone event appended by <em>system</em>. It is an irreversible operation, and you won’t be able to use this stream. Still, to erase it from the disk, you need to run a log compaction process <a href="https://developers.eventstore.com/server/v23.10/operations.html#scavenging">called EventStoreDB <em>scavenging</em></a>. You can also use <a href="https://developers.eventstore.com/server/v23.10/streams.html#soft-delete-and-truncatebefore">soft delete</a> that doesn’t add tombstone event automatically, same with <a href="https://martendb.io/events/archiving.html#archiving-event-streams">Marten archive stream method</a>.</p> <p><strong>EventStoreDB also allows partial removal using <a href="https://developers.eventstore.com/server/v23.10/streams.html#soft-delete-and-truncatebefore">truncate before stream metadata</a>.</strong> That’s what I recommend, as you can append explicit <em>Tombstone Event</em> and mark all events before as eligible for <em>truncation</em>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d8223935e123a32b1ec83d1174f7a0f8/b58f7/2023-11-26-tombstone-event.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACMElEQVQozz2R608TQRTF9w83Jn40GmMMEQNpLIoiakOQghYo0FKKtHZpaUt3S1t2yz7mPbMzO93loZgl0eTk5J4PN/d3cg0uEyo0IBEkEWEyufnTPD3bKe/vlitfvq6Xdg8OqvXi953NHzv22GUygSSSOpX6mvDY+LcpQxL5kPmAdPv2xlapenRSO25ubJYOqvVGq105PO70bNeDgIjD+kmnZwmVGgBLxGIsZiRKIdOOjz3IAyxDIgGNEdc+Ej4SAZYe4NOAiuR+Ib+8slqIZ3cG5nEYBGEYBFfueaflXFhjq2f32s7I6poNzxkPzszL4WBs9S/Ou97VZNj/lZt/vvZtQ+rbDLtv1hu1slkplpZeNIrv9j693v04d7K5vL74tLG1XHr/sraW2//8ZvvDXKO8Usk/bhWejbwJE4nBZYIpx4QihCnGp6a5XSqFIfCcydSdQgghACDzTAAigEIv8AEVhMUGIApzTUVKeBLf3m/v11/NL3peMLEHVKaYa8Q15rMH11hoImaYJ4gqSCIDMX0F8TB0xoED/Mvi+mpuKQ8gBVhAIgGRkEjEFKTyIUaIZpHwGDNliOjGnHRy1YXCUX7v7aP6yhN7aon4DnEFmUIszn7BNWI6u8Yk5jHJSDVhKsOeQjwKp+PAHV9Yk5E1vHR+Ntutdr/Ts5tm1/UR5nGGQP9LwYwoMmoD0h/5rhsIdS2T30LfUZnQaMZlwuSMyWwQKuEqESrNPE65SmnWXP0FqOI8/DSqVucAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="truncate before and tombstone event" title="truncate before and tombstone event" src="/static/d8223935e123a32b1ec83d1174f7a0f8/a331c/2023-11-26-tombstone-event.png" srcset="/static/d8223935e123a32b1ec83d1174f7a0f8/36ca5/2023-11-26-tombstone-event.png 200w, /static/d8223935e123a32b1ec83d1174f7a0f8/a3397/2023-11-26-tombstone-event.png 400w, /static/d8223935e123a32b1ec83d1174f7a0f8/a331c/2023-11-26-tombstone-event.png 800w, /static/d8223935e123a32b1ec83d1174f7a0f8/8537d/2023-11-26-tombstone-event.png 1200w, /static/d8223935e123a32b1ec83d1174f7a0f8/b58f7/2023-11-26-tombstone-event.png 1324w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I want to highlight that we should NEVER put PII information like email, user name, or insurance id into the topic or stream name.</strong> Because of the tombstone event, this topic or stream id remains. Therefore, hash this information if necessary.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ce51a0f544f91319a88d1895a43a72b7/536f2/2023-11-26-id-with-pii.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACH0lEQVQozzWQTW/TQBCG/afpodw5IC4cEHeEoBVqqZKmTdLvlhYamkBSjJPWiZPYa8/Ofu86dhIuyBVIc5nRPNL7Ph7XeYoyRQVMM2FMvnSLtXElV3k0hzBKen0/zrhZrIRZCFMqW2hbSLOg3HgxYYft05PzLzOC19+6p+fXRycXt3c/pV12uoPNzefPNjbu/aFdrEfh/FcQ+sNxikqovIJRuNE4/jEIlFsPw3mnOyBUxlQKtxxN4tdv3r54+ery63e1+NMbDNsXN71BQJhG4YBpj+kCIBuNAkQWTcbhOOTKCJCMSopq5+Onne2d6CFSwvr+w6T/29mSccNQVrAwxXQa7ddro6F/0Kg12604DNO9Rrq3nx5f4LsPcq8B77fIbi1ttMjnetJopQdHUD/MRhNP2ZJLDYhSG+QcmeDSSluJUW5JyYxmwISTpuAqF26pbFmZUzll2gNuqXBU5lQ44O7f+v8I86DT6Zxd3d7c9o5Pr2YJMJlXP8xkVHnAcwAkSZJRBMgyABSaMaSQUCbT+RiyhCuLQmdAUWgUFqXj0gE3HtfldDqt1fcfH4LDZvOg2Y7n07RfI3dbSW+X+CfpoJ4FZ+S+RbrbpF9HYZlaVDDTXsZskmbTaBwTEkWTOJ5TJgEQUAJlwE3GFKBKKWZQJaP8qRQzgNq79DGYkFkC0pW2WEtbCl0ot9L5SuUrZZfKVaPdSuVrla+lK6UtnoSZv61EQ3TD+sUgAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="id with pii" title="id with pii" src="/static/ce51a0f544f91319a88d1895a43a72b7/a331c/2023-11-26-id-with-pii.png" srcset="/static/ce51a0f544f91319a88d1895a43a72b7/36ca5/2023-11-26-id-with-pii.png 200w, /static/ce51a0f544f91319a88d1895a43a72b7/a3397/2023-11-26-id-with-pii.png 400w, /static/ce51a0f544f91319a88d1895a43a72b7/a331c/2023-11-26-id-with-pii.png 800w, /static/ce51a0f544f91319a88d1895a43a72b7/8537d/2023-11-26-id-with-pii.png 1200w, /static/ce51a0f544f91319a88d1895a43a72b7/536f2/2023-11-26-id-with-pii.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Log compaction is a resource-consuming operation that can take some time. It’s better to do it regularly, then the amount of data to compact will be smaller. It’s also better to do it when the traffic in the system is smaller so as not to impact our tool performance.</p> <p><strong>Log compaction sounds like a dangerous operation, but it doesn’t have to be. Usually, it allows creation of a new log based on the old one, keeping the previous one untouched.</strong> That’s also aligned with the immutability principle, as we’re not changing past data; we’re writing the new version to another place. Of course, we need more storage, which can cost us more, but we’re also getting a certainty that if something goes wrong, we can restore initial data, tweak it and rerun log compaction without the risk of losing data. That’s fine as long as we remove the old log within the privacy regulations retention policy. Of course, tools also allow removing the old log after log compaction or trying to override it as it goes.</p> <h2 id="forgettable-payload" style="position:relative;"><a href="#forgettable-payload" aria-label="forgettable payload permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Forgettable Payload</h2> <p><strong>Knowing that there’s a way to erase data from event-driven tools, how do we proceed with it? One of the potential options is <a href="https://verraes.net/2019/05/eventsourcing-patterns-forgettable-payloads/">Forgettable Payload, explained by Mathias Verraes</a>.</strong> This technique isn’t foolproof, but it can significantly simplify data management.</p> <p>When sending events informing other systems about changes made in our module, instead of transmitting complete personal information like names, emails or insurance id, we could replace these details with a <a href="https://en.wikipedia.org/wiki/Uniform_Resource_Identifier">URN</a> or link targeting where this information can be queried and accessed.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a62a27f03fce5a4d4dc8eaae50e98c0f/536f2/2023-11-26-forgettable-payload.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACDUlEQVQozx2Ry04bQRBF5/93iZBQlAgEyBKJlE02SRBBGALYBoIhYIM97+7q6uf0Y3osm0UUDfu6dXXPSWxYGddx5ZWNBMSPo19Pz+nF6Pr45Oz70cn17cPjfHF+ORnfTMc3dxUIbVvrO+s7oX2ibeQ6cOWFaWcv+cHg8NPO3vut7f3B5w8fd3f3B3sHh++2tnf2Bl++fmPSo/ZcB21jH0bphA6q6aSJwgQXX127se3Gd6/d+p+NG8awLNIiT8si40iRUUoJ54KrkMgmatcpG5WNb/+cNAFVoMKJJmob5k8Pk9Hl+OrqYXqHAIxSoBWjlGufyKaVTauaTtmV1JaRkjNaUPlSCq6DNA5I9fd+en/3ByjhDBAZAkEArkPS+FUTVrXgGSNcKWQE6kw3QbuVDevGBYEwf3o8Pvo5uer7z4fD59mj5NiHufZCh3mZTtNZURVACa2WeQWznDPppbF1VYxHo/Ph6e/h6e31JFsuqzwFSlD6hOsgTFujLBgyLpBWSAquHaqeqlC2yPN0mWbLZZZmZVly5AiUA+WmTaTpN0sTZROV7YRqGFSEYPW2mStL6xxJAVVKyyUtF7RcQLlAWgvdJr065fq7N4HSRGVXBHReYi9fe8YlMOwZAwCwnjcgco3KJWczOctoXoPxnYtrkM0irwlKEIYwWTNJuaHc1EwRNLZdu7ix7dq4Tij/H2OZRpasmrCoAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="forgettable payload" title="forgettable payload" src="/static/a62a27f03fce5a4d4dc8eaae50e98c0f/a331c/2023-11-26-forgettable-payload.png" srcset="/static/a62a27f03fce5a4d4dc8eaae50e98c0f/36ca5/2023-11-26-forgettable-payload.png 200w, /static/a62a27f03fce5a4d4dc8eaae50e98c0f/a3397/2023-11-26-forgettable-payload.png 400w, /static/a62a27f03fce5a4d4dc8eaae50e98c0f/a331c/2023-11-26-forgettable-payload.png 800w, /static/a62a27f03fce5a4d4dc8eaae50e98c0f/8537d/2023-11-26-forgettable-payload.png 1200w, /static/a62a27f03fce5a4d4dc8eaae50e98c0f/536f2/2023-11-26-forgettable-payload.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>It allows us to demonstrate our control over the data explicitly. We can also have easier control by defining authorisation rules, etc.</p> <p><strong>We also get centralised control around personal data and consent.</strong> When a user requests the removal of their information, we can delete it from our database. If we’re using Event Sourcing, we can utilise the described above log compaction. If a user tries to access this data, we can either convey that the information is no longer applicable, return a null value, or anonymize the data as preferred. Thus, we maintain control over the data, offering a practical solution and providing us with useful tools for data management.</p> <p><strong>It’s not a perfect scenario, as we’re vulnerable to race conditions, the reader may cache the result, and we’re losing control of the data.</strong> This pattern is not entirely foolproof (read more in <a href="/en/clickbait_event/">Anti-patterns in event modelling - Clickbait event</a>).</p> <h2 id="archive-data" style="position:relative;"><a href="#archive-data" aria-label="archive data permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Archive data</h2> <p><strong>We already learned in recent years that we should segregate our system behaviour.</strong> We should also break our data model into the write and read models. That enables us to use different tools for different needs (e.g., PostgreSQL for writes, ElasticSearch for search, etc.). It also enhances resiliency, as we can cache data from another service. We can also transform what we stored, getting a new interpretation. Yet, writing and reading needs are just one of the factors around data governance.</p> <p><strong>We should consider data considering its temperature. We should decide which part of our data is hot, cold, or warm.</strong></p> <p><strong>By ‘hot data,’ I mean transactional data – all the information we need to make decisions.</strong> Remember, in Event Sourcing, we make decisions based on events; in our regular systems, we make them based on rows, documents, etc. I</p> <p><strong>Warm data is the one that either changes rarely or is used just for reading needs (e.g., analytics, read models, audit).</strong> If we won’t modify this data, why keep it in the write model? Maybe it’s fine to keep it in the analytics database. We should avoid keeping redundant data and consider using the same type of database but with a smaller instance size, thus saving costs.</p> <p><strong>If the data is cold, we are not using it at all, and we don’t need to keep it in our transactional database.</strong> We can archive it. An archive process doesn’t mean we remove data; we’re moving it elsewhere.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/317aeab67018211a07245fb000a92f7f/536f2/2023-11-26-archive.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACS0lEQVQozx2RSU8UQRiG+zd48uf4GzyrB6OJBz15NUSUMCwuhJCoETxgSAwxRmO4KDjAjND2bDADszC9Ti/VtXRVdXVN9yAjmJ7kvT75vvd9FCbSiA9DEoeE03gYyxFPstHfq/KBtvHp806xvLNb6lueHI0Jk4gmOBJMpCweQhIrhMmQiJAIgHkAmRtgN8COh6qNk72yWiwd7u4f6pbrh5E5AJaHPEh9xAlLcjhAHBKBqURUAsR12w8AGHggQMyHTKs11crRyemZPQCuD70Ac57EcU6GmCuISsKGOI/EbCjkcPVXtqUGrudVKrXTTi+AkU1HLVe0bGIBXupF348RZQKgHE4QTUIiUJQgKilPfhyJvaqhqaXS7vb8zBP1UGuuvG7XW00bdw13edt6uNkXsQSYKzROWZKJ9ILLjIoUM3l+Mda0ypvl2fnCzPvVNXsQFJdfOd2+PP8nkoxPBqZc5p1hlADEVK2u2z6MRIhZgGgUpwdq9eu3LeC5pq53+7oHGcQ8r5pHQBwDxJSQ5D+3OroXMibHkI1gfAn5iGdjeXFlGnq3Wdd7HSzOQ55CnqE4Q0ySvGl+WaBcoCyW9pcKj8++3Lykv7E7gLYd6Kbb0wednndmQMsBhh1aDrIdROKJYD5RFUnHsVembjy7d+3FbeXn7PWTj1OND9Od9YX6u0Lj7aK+8bK3OddeX6qvPW2vPwe6CekQQKbst2HH9A3TnpuZvnvn1oP7jxYKi+2KZtU0q15zGvVBoxEcH5mthtFq9jXVqP7Bk1Eg5v8BuRoyCPOqpH0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="archive" title="archive" src="/static/317aeab67018211a07245fb000a92f7f/a331c/2023-11-26-archive.png" srcset="/static/317aeab67018211a07245fb000a92f7f/36ca5/2023-11-26-archive.png 200w, /static/317aeab67018211a07245fb000a92f7f/a3397/2023-11-26-archive.png 400w, /static/317aeab67018211a07245fb000a92f7f/a331c/2023-11-26-archive.png 800w, /static/317aeab67018211a07245fb000a92f7f/8537d/2023-11-26-archive.png 1200w, /static/317aeab67018211a07245fb000a92f7f/536f2/2023-11-26-archive.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We might need different strategies for archiving our data. For instance, archiving may imply that we have some audit obligation to keep our data for a set duration, e.g. ten years. It is a nuance with GDPR; in some regulated industries, you can’t just remove patient information in healthcare. GDPR doesn’t mean you can never keep data; if laws mandate keeping specific data, that takes precedence.</p> <p><strong>Still, the need to keep data doesn’t mean we must keep it all in one place. The data could be stored elsewhere.</strong> Why pollute your primary database with data needed only for auditing purposes? If we keep unnecessary data in our database, <em>“just in case”</em> we need it in the coming years, it clutters and slows down our system.</p> <p><strong>So, thumb-rules are:</strong></p> <ul> <li>keep in write model only data you need for decision making,</li> <li>if you need to modify data from time to time, you can just leave a summary of the information you need for decision-making; you might not need all past data.</li> <li>In event-sourced systems, that means keeping streams short by introducing temporal aspects into them,</li> <li>if you need data just for reads, keep them inside read models, purge them from write model when you don’t need to make new decisions out of them,</li> <li>if you still need events for rebuilding projections, consider storing summary event and removing all past events,</li> <li>if your data is cold, just kill it. Move to backup or cold storage and call it a day.</li> </ul> <p>Want more? Check my talk where I explained that in detail:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/gG6DGmYKk4I?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>Or read my other article <a href="https://www.eventstore.com/blog/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention">Keep your streams short! Temporal modeling for fast reads and optimal data retention</a>.</p> <h2 id="crypto-shredding" style="position:relative;"><a href="#crypto-shredding" aria-label="crypto shredding permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Crypto Shredding</h2> <p><strong>There is one more technique that could simplify things, known as crypto shredding.</strong> No, it’s not blockchain technology or something related to <a href="https://en.wikipedia.org/wiki/Shredder_(Teenage_Mutant_Ninja_Turtles)">Teenage Mutant Hero Turtles character</a>. Crypto refers to cryptography, the original term. Let’s explore this concept step by step. It’s another approach to making life easier and removing something without actually removing it.</p> <p><strong>Instead of keeping the PII information raw in our events, we could encrypt it.</strong> We could generate a unique encryption key for each user to encrypt the users’ events payloads.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/cec16bdfe37462a13809b94efd86d68e/536f2/2023-11-26-cryptoshredding.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACBElEQVQozy2RS0/bQBSF8/vbRaUiIXXbosKKRVuEoKKlBAIppHmR2I4nsZNMPI87d942fWyqBKS7/XTPd07L+AZtzZXjymkbL6863YdBp3v/mOSX7dv+aPrtx83ZxWVGStCBKwfojW+Mq4VyLWUiV56BExhSsvx0cvbq9Zu9/XefT84PD48/Hh6/3dv/cHD0/uBoNl8zcJW0DJwycQtX0gr0YGqBUaBXJrr4x8W/FG2i5jNVgnsK9T/tG4kedC0xbD8px8G2pA7K1GBqZesN+JTaGbXDQg8L2VuMevNJL5fj0mTUksqDjsrWYCKgf4GlDgKDMpFQ3U54J5PtRIwLNLbhEO8ycZPJ60TcZbICByZKHQDDFtau0b7RrjH+CUzdn7HbhE1KNaOm/bj5mbK0xLuE/SKSbDS6xoQn7Rp8dubKCfQCA0ePJo7mcHq/uBqvpws5nMtBLiYFXPRXX3tlUkq0USi/PXBMmhZXW1IoLzFQ6SZL3c1EunHZSqcFz5diUqoegcECSeUZeKm91FHtZmtJfHb2oMN8Y08fyvN+eZ3be4KkrGYF/z7afOmSTo69ZVgLu3OuX+BKOgaWg5PoV8wMczbIqyER6RJ2IcO0kP0ZGxI+XojnXflLbNu6msopoeW6KlasWFe0AsoVrQRlYs0UZcAEVlwxgZRJJrUJv7XfFQbuP7CoSaHANcddAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="crypto shredding overal" title="crypto shredding overal" src="/static/cec16bdfe37462a13809b94efd86d68e/a331c/2023-11-26-cryptoshredding.png" srcset="/static/cec16bdfe37462a13809b94efd86d68e/36ca5/2023-11-26-cryptoshredding.png 200w, /static/cec16bdfe37462a13809b94efd86d68e/a3397/2023-11-26-cryptoshredding.png 400w, /static/cec16bdfe37462a13809b94efd86d68e/a331c/2023-11-26-cryptoshredding.png 800w, /static/cec16bdfe37462a13809b94efd86d68e/8537d/2023-11-26-cryptoshredding.png 1200w, /static/cec16bdfe37462a13809b94efd86d68e/536f2/2023-11-26-cryptoshredding.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>When users request to remove their data, we don’t remove them from our database; we remove their encryption keys instead.</strong> This renders the encrypted data indecipherable, effectively deleting it without physical removal.</p> <p>Of course, we don’t need to encrypt the whole payload; we can select the specific fields we’d like to encrypt. We can plug into the serialisation mechanism and extend it with encryption capabilities, automating the whole process. For instance, you can mark your contract using C# attributes and Java annotations or use Protobuf with custom attributes. You can denote which fields contain user ID information, like customer ID, and mark which fields contain PII.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/02004646682b5e425aa20d5565c915bc/536f2/2023-11-26-cryptoshredding-payload.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACEUlEQVQozy2S22sTURjE9/9HxAcp4rPQWPGpogR7TUlrSmy1t2xtNjndbHbP5Tv7ne/cdjcpVRqEeZmHYYYfk6BtaooVGK6MRn84GI7Gv85G45s0OzoZXVzdHRwP+3tH6QOTteNAqrbGRbRRgE00eqEtmFjbJmPlt/7+q9dv3m6939398qHX6+183tp6t/1xZ7v3aZ6XQttKEQfS6KW2yca4jJX3szx9eJyypQCi+NRaahq3fnpePf/tnp679bppg3VBaMfBSrBCmQRMENoeDs6+H5x87e/3947Gl9eXV5Pbu1TlZZUXgksutdZI1iisAYNCr7TlyiQSvURvXGf8yvgVhRW6blHBYjYTk/vl5BbmU3hkkuVQ5MC53ISh9kJRUlM0vluUMmMFK/h0XixKZXzbIPlKG6k8YmN9cKENkQTUtkFqtAkvwDgQYMjY8uT0/HBwOhpf3aTZ+cX1PL1vlNFcBmM67wLFSAG55LWT2gmwpcREaCe0AwyVolIimODiulJGL6vAdV2Jl2YXAsU2RMOl0O7/bKBE1E5hKKUZ/vh5PBz9mS2yx2WWV6qsHCsVy2lRhKKyhfCVRAGi9oBBo+dASSlJAC1KuEmz2zSbsiUrxMXvyXQ662zr0TTWtsY67RsfbYiVIqGdBMulSQYTOckKlpfkOxdX6BrjWwqrzY28scG4+CIb0HqkgK6pbdwAo38kSFGc1tp0JQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="contract example" title="contract example" src="/static/02004646682b5e425aa20d5565c915bc/a331c/2023-11-26-cryptoshredding-payload.png" srcset="/static/02004646682b5e425aa20d5565c915bc/36ca5/2023-11-26-cryptoshredding-payload.png 200w, /static/02004646682b5e425aa20d5565c915bc/a3397/2023-11-26-cryptoshredding-payload.png 400w, /static/02004646682b5e425aa20d5565c915bc/a331c/2023-11-26-cryptoshredding-payload.png 800w, /static/02004646682b5e425aa20d5565c915bc/8537d/2023-11-26-cryptoshredding-payload.png 1200w, /static/02004646682b5e425aa20d5565c915bc/536f2/2023-11-26-cryptoshredding-payload.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Once established, we use this customer ID to find the encryption key stored in a secure key management store, such as <a href="https://www.vaultproject.io/">HashiCorp Vault</a>, <a href="https://aws.amazon.com/kms/">AWS KMS</a>, <a href="https://azure.microsoft.com/en-us/products/key-vault">Azure Key Vault</a>, or <a href="https://cloud.google.com/kms/docs">Google KMS</a>.</p> <p>The general flow would look like this:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/fa73a459aef2de20d1ff84bf95942871/536f2/2023-11-26-cryptoshredding-encryption.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACHElEQVQozx2SWWvbQBhF/ftLoVDaUvoDnKaQPDQkxenmOs7ixEm8S7ZkS7JmRrN9s0qy474UFS6X+3I4L7el3Q5szaRl0ipbdXs3g+HodjCcBXG3f/c8Xfz+c9350Q2jhCtPwQpwxtXa1Uy6ltQlFbYQloFbxtnZeefV6zfvPnw6u+gcfT5tH528ff+xfXzSPj7ZbCmVnghHpQNdcXAtKqw0tfb7/34HpnLViyv/YrCBXK917r22UDCSoiyqrCitbOJMY8ZMJTmNU5xhRmQZIrMiZpyoScqHm+k4naEkXM1Hy/lou1m+eDiU+uChssDAtaIEfT3vtL+cXv7shinvB+w6ZFcBm6ZKmR3ostQsi8MkCmoLh8qgNM7iYOdVYw7j9E//djAc3d0/pZiP1/xmQeapjJC+muGHENdObuNl79f3yePd0+AmDmeGo9pKBmWLcI0pYKYQVWCqyVp8e0h603yR8HEsxiuiRbFajCeP95OnQRxO917tnSg1p9K1GHiuSt60x9zNM3W/5EvsljmEG7bJqaAYGAaKtaBO8Z3XOweVkQx8i6sGY9IJ7dfYXAzTy+e0H5lhrKK02GTEq2Jvaa1RrXGtUAWoVnmlWQMTYaloHsKVy6kexXQcFZM1CzMJphLaM8YJwYSggmANUitQCpRuqFYv4MEaZTlJ8yLNCS4EoRIVHBccNUMiCtsCMNNbIgthTXlQ7kWa5pT/AB7xRqgiFmmAAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="encryption" title="encryption" src="/static/fa73a459aef2de20d1ff84bf95942871/a331c/2023-11-26-cryptoshredding-encryption.png" srcset="/static/fa73a459aef2de20d1ff84bf95942871/36ca5/2023-11-26-cryptoshredding-encryption.png 200w, /static/fa73a459aef2de20d1ff84bf95942871/a3397/2023-11-26-cryptoshredding-encryption.png 400w, /static/fa73a459aef2de20d1ff84bf95942871/a331c/2023-11-26-cryptoshredding-encryption.png 800w, /static/fa73a459aef2de20d1ff84bf95942871/8537d/2023-11-26-cryptoshredding-encryption.png 1200w, /static/fa73a459aef2de20d1ff84bf95942871/536f2/2023-11-26-cryptoshredding-encryption.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Deserialisation is a similar process; we take the user key from the key store and decrypt the data we get in events. If the user key is unavailable, we can get regular data and assume that PII is not available or generate anonymised data on the fly.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0fd272149b891b6366bfad5ba711de23/536f2/2023-11-26-cryptoshredding-decryption.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACIklEQVQozxWSS1PTUBhA8/t148gMC5fqSFcuEGagUrBgoQVCW0qb5tG8mnDfN999JRRdKDNnezZnjteYVoLDDAgDCXZ4dX3rz24m98sgGV6Np4vg4nLUHwzDJGeNJVwxocG0jW4JVx5rHJVvCNVFaXV43H/3/sPe/qfDo9Ne7/vBwfePe/tfvvU+f+1FaYW5QgwwV6KxVGgPlCVoW5ebMg9xnbVGvHb6787p1gV8E4mC6c62fxrdUWE4OCotYkC4wgw8Z3WVBtFyFi1nHJWvrum02GmmDdyli4d06Sd0UTRhpRJkqHRctWB3UnWEqTfZSrxN420WS1J1WnSm6RRtnRXwQrgbr+kopL8DchNSxM2tPz//NTo7v8wK5O06h8rNef9kNBwMB/310ywJnlARGWsuF/VdiINCjAPkxzRFgBlcjibHJ2c/jk/vpwuvdYbVuT8Zzf3xc5F0ijtgL0BkA9OYzRO8yvhgVv7087DklKv5MlqG6dMqjtOt54xWHK0X83i9TKNAknpn1c5wIeQ6o3FBVrl4SNgsFQnSmGsOjkMrVftW2xkFtCBlyKqIVRGQrG2eu6bSCqKcRDm+eKyOJpvrWPiFqQhwaZi0XBrMlCfBGq2sUc4oo0E1opFcAWjb8sZRYVYZnUboMcGLlCCmqDBEaMoVpuANV3S1qbMtyrY4LXFRs+KZ5zUtalJhXiGOqEBEICprxDBrwL5I3Qpw/w/7B2fBRsJQ2I9CAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="decryption" title="decryption" src="/static/0fd272149b891b6366bfad5ba711de23/a331c/2023-11-26-cryptoshredding-decryption.png" srcset="/static/0fd272149b891b6366bfad5ba711de23/36ca5/2023-11-26-cryptoshredding-decryption.png 200w, /static/0fd272149b891b6366bfad5ba711de23/a3397/2023-11-26-cryptoshredding-decryption.png 400w, /static/0fd272149b891b6366bfad5ba711de23/a331c/2023-11-26-cryptoshredding-decryption.png 800w, /static/0fd272149b891b6366bfad5ba711de23/8537d/2023-11-26-cryptoshredding-decryption.png 1200w, /static/0fd272149b891b6366bfad5ba711de23/536f2/2023-11-26-cryptoshredding-decryption.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>We then encrypt the data, a technique similar to the forgettable payload method but neater because without access to the encryption key, decryption is impossible.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a541ff8edd2feec4ce4ae1bf9214e705/536f2/2023-11-26-cryptoshredding-removal.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACG0lEQVQozx1SbU/TYBTt7zcmJmpI9CsgEvxgJEggIjAQnIytG8xua7d1dOvz/v70abduzrQ359NNzrn33HM9oXOuHKIKUcWVvWrcNVv+fbPVD6Lrm2bnKbhs/Dq/uB5GMREWUYWZlsYJ7RDTHhMZohoQhZgdRi/H389evX7zfufjt5OzvYOjvYOjt+92dj993t0/HE3mkKqUKEgVExYz7QEsETNUZphbJrNiVW7+bTfbbVGu3XqZr1ebbVWrcmNdwZTD3EKqMNWISo+IigaIRFxLm7vlOitK40qbr7SzNZYmX2XLMl9tuHJUZkRawg0k0qPStbvBz8ZdGC+EKbjJpS2EyY1brsrNcrlWthB1R5iCiIwIS0VG6/neS0p+XN4efvn68NhlKhvEuD0CYUJfkLr/C9oRiVPhR+h5SuZY1hI5VzmT1aW8IIw7vYH/NGh1+nNAehN68mdy3UueJ9gPkT+C/Qm5aM/OW9NgiisO04gZRHWKa89MOiocUy4lpj/jDyM0BGY0BuHl/fim1Y/g45R3YhFCC4jG3NSbV7F5lQwzkGrCzXghT1vxeSe+DWUr4tEMDhPaPPt98+GgvX/sn94lSNHKdsZE7bnKjVQfgrlOoOxG0A9Bd4wGM8plhrgJRvOnbvTci/r9GBCFmYFMY6ogll4jwMF4PkvSOAFxAhaALiCdA1wB0gWgKRUplYCrOWaACGkLrl19MPUfSi9MTuDiRnUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="removal" title="removal" src="/static/a541ff8edd2feec4ce4ae1bf9214e705/a331c/2023-11-26-cryptoshredding-removal.png" srcset="/static/a541ff8edd2feec4ce4ae1bf9214e705/36ca5/2023-11-26-cryptoshredding-removal.png 200w, /static/a541ff8edd2feec4ce4ae1bf9214e705/a3397/2023-11-26-cryptoshredding-removal.png 400w, /static/a541ff8edd2feec4ce4ae1bf9214e705/a331c/2023-11-26-cryptoshredding-removal.png 800w, /static/a541ff8edd2feec4ce4ae1bf9214e705/8537d/2023-11-26-cryptoshredding-removal.png 1200w, /static/a541ff8edd2feec4ce4ae1bf9214e705/536f2/2023-11-26-cryptoshredding-removal.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>To see how to perform such transformations, check <a href="/en/simple_events_versioning_patterns/">Simple patterns for events schema versioning</a> or the complete implementation in <a href="https://www.eventstore.com/blog/protecting-sensitive-data-in-event-sourced-systems-with-crypto-shredding-1">Diego Martin’s article “Protecting Sensitive Data in Event-Sourced Systems with Crypto Shredding”</a>.</p> <p><strong>Crypto Shredding is a conceptually simple technique, but it offers many possibilities. For instance, <a href="https://event-driven.io/en/gdpr_for_busy_developers/#what-about-backups">handling backups</a> gets much more straightforward.</strong> If our data, which should be removed from backups, is already encrypted, we need not worry. Deleting the encryption key effectively removes the data from the backups. The classical approach would require restoring and cleaning up backups and creating new ones with the cleaned-up data. Crypto shredding is a much more efficient solution.</p> <p>One potential downside is that encryption and decryption must be done on the fly, which has some performance impact.</p> <p><strong>Secondly, some people say that quantum computing threatens the crypto shredding.</strong> Once the encryption algorithms are busted, the data will become available again. We should use secure techniques like user key rotation (e.g. every 30 days) and the most secure cryptographic algorithms. We get from the dedicated key management services. If algorithms are busted, we’ll have more urgent issues to fix first, but in the worst case, we can encrypt data again. Still, I’m not trying to downplay that, as…</p> <p><strong>Using crypto shredding shouldn’t stop us from using other data governance techniques, like applying retention policies to our data and separating personal information from regular data.</strong> Without separation, we’ll get a manageable amalgamat of data. It will be hard to find which decryption key to use and how to deal with the data. If we have a retention policy defined and applied to our events, then even busting encryption keys won’t harm us, as data won’t already exist. In essence, we should strive to be good stewards of our data.</p> <p>Crypto shredding isn’t a foolproof solution, but it is likely the most straightforward method that can be applied in event-sourced systems, general event-driven architectures, and other databases. We could use it perfectly well to <a href="/en/key-value-stores/">key-value and document databases</a>.</p> <p><strong>We generally should not share too much with external systems.</strong> If we’d like to use crypto shredding there, we need to ensure that the user key lifetime is not longer than</p> <h2 id="sharing-events-externally" style="position:relative;"><a href="#sharing-events-externally" aria-label="sharing events externally permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Sharing events externally</h2> <p><strong>The first rule of sharing the users’ data is: you don’t.</strong></p> <p><strong>Too often, I see people trying to distribute data between both system modules and external systems.</strong> I noticed that with event-driven tooling, people are even more open to that. Maybe that’s because they’re treating the messaging tooling as something internal and thus more secure. And that’s a false view.</p> <p>Once we publish data, whether we use patterns like Crypto Shredding or Forgettable Payload, someone can access the data and store it insecurely. Of course, if that happens inside our internal modules, then we can try to control it.</p> <p><strong>We should always publish an event where user consent was removed, requesting other modules to remove PII data.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/146ecef4254d2baf358a6c094a1bd3cf/536f2/2023-11-23-removal-notification.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB20lEQVQozy1Q2W7bMBDU/39Lixh2XB9JWgR9Dwq3tmWJui9yubypI3kqJBtYDLgkZ3dmAu2mhoqP0yWMs1ucXW6E5BXJynNI/pyuYZRlZXtLyrRoo6S8keJ6SyKSVS1w6QJphiRv1tvj8+519bzf7F62+9fVZve03n172qy3h+Pb+3Z33L/8+nH4eXh7X21231fP52ssdB+0TIP02n9JO6HyUFNpRjSjdp/KfwkzculRD1z1Qo/KTspNwowofcd1ANJx3YPyXPXAdXG91UXbUMhpx4RFPYDqQXpUHnXPlefSg3SAht7JIB27X801H8qWXVPSccW4BlAdU3GJeSPvr/MI4WayNIO0wwPtgLoXM3rKULqpzasmThmTNTNhXJOk0X4SZhDKM24CioYJy4Rb0FK0IBzJ0kv0L8/zOquKKCnTquG2pqIDBcIxtIybFlRA0bF7j/auXOghTsjp70ccxU3ZVWmVx3mYwZm0pORMWFAOpaNoAhCL52XzbGYJphOmAtFQrNKqCElbdqjHoqZhlN6jfZBb0B3XDA2dly/icXaxDPUUTV3RPK1JVBNSzT+Fo4vsDnTw+wLXpM7KRpheuVHoHnUvzaDcUnbU/acwQ9uJukPUXvtJ2kdg/wFg4VGpAH6H9QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="removal notification" title="removal notification" src="/static/146ecef4254d2baf358a6c094a1bd3cf/a331c/2023-11-23-removal-notification.png" srcset="/static/146ecef4254d2baf358a6c094a1bd3cf/36ca5/2023-11-23-removal-notification.png 200w, /static/146ecef4254d2baf358a6c094a1bd3cf/a3397/2023-11-23-removal-notification.png 400w, /static/146ecef4254d2baf358a6c094a1bd3cf/a331c/2023-11-23-removal-notification.png 800w, /static/146ecef4254d2baf358a6c094a1bd3cf/8537d/2023-11-23-removal-notification.png 1200w, /static/146ecef4254d2baf358a6c094a1bd3cf/536f2/2023-11-23-removal-notification.png 1350w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>For external systems, I discourage you from sharing data. But if you really have to, as I wrote in <a href="/en/gdpr_for_busy_developers/#sharing-pii-data">previous article</a>, at least ensure that they allow you to request the data removal. Check if they have certifications of common standards like <a href="https://en.wikipedia.org/wiki/System_and_Organization_Controls">SOC 2</a>, <a href="https://en.wikipedia.org/wiki/ISO/IEC_27001">ISO 27001</a> or <a href="https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act">HIPAA</a>. It won’t give a full guarantee, but it’ll show you that they have procedures and that you can demand to apply them and place them in contract terms.</p> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>After this article, I hope you see that applying privacy regulations to event-driven systems differs mainly from the technical patterns in handling them.</p> <p><strong>The general practices around data governance, like segregation, data recency, and privacy by default, remain the same.</strong></p> <p>My perspective on GDPR is that it serves as a privacy framework. Most of what I’ve discussed is not specific to event-driven systems but relates to the broader context of transparency in our operations, empathy towards users, and building credibility and trust by applying sound data governance practices.</p> <p><strong>Ultimately, it’s all about respecting user rights</strong> So, are we the baddies in this scenario? Possibly, but we don’t have to be. I hope that after this article series, you have gained some tools for working with data responsibly. Even if you don’t implement them immediately, there will likely come a time when they will be necessary.</p> <p>You can also watch:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/7NGlYgobTyY?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>Other resources you may find useful:</strong></p> <ul> <li><a href="https://www.youtube.com/watch?v=l6ueOeoW7XM">Bram Leenders - Scalable User Privacy: Crypto Shredding at Spotify</a></li> <li><a href="https://www.michielrook.nl/2017/11/forget-me-please-event-sourcing-gdpr/">Michiel Rook - Forget me please? Event sourcing and the GDPR</a></li> <li><a href="https://www.michielrook.nl/2017/11/event-sourcing-gdpr-follow-up/">Michiel Rook - Event sourcing and the GDPR: a follow-up</a></li> <li><a href="https://medium.com/sydseter/gdpr-compliant-event-sourcing-with-hashicorp-vault-f27011cac318">Johan Sydseter - GDPR compliant event sourcing with HashiCorp Vault</a></li> <li><a href="https://verraes.net/2019/05/eventsourcing-patterns-throw-away-the-key/">Mathias Verraes - Eventsourcing Patterns: Crypto-Shredding</a></li> <li><a href="https://www.eventstore.com/blog/protecting-sensitive-data-in-event-sourced-systems-with-crypto-shredding-1">Diego Martin - Protecting Sensitive Data in Event-Sourced Systems with Crypto Shredding</a></li> <li><a href="https://danlebrero.com/2018/04/11/kafka-gdpr-event-sourcing/">Daniel Lebrero - Kafka, GDPR and Event Sourcing</a></li> <li><a href="https://www.youtube.com/watch?v=FTcBa-2-I2c">Stuart Herbert - Event Sourcing and GDPR: When Immutability Meets Reality</a></li> <li><a href="https://www.confluent.io/resources/kafka-summit-2020/gdpr-compliance-transparent-handing-of-personally-identifiable-information-in-event-driven-systems/">Masih Derkani - GDPR Compliance: Transparent Handing of Personally Identifiable Information in Event-Driven Systems</a></li> <li><a href="https://www.infoq.com/articles/gdpr-with-spring-and-aop">Harish Kumar - Easy Implementation of GDPR with Aspect Oriented Programming</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[We introduced support plans for Marten]]>https://event-driven.io/en/marten_support_plans/https://event-driven.io/en/marten_support_plans/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/00a528532d3dbd1dee8848276fae0642/a331c/2023-11-19-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACUUlEQVQoz2NgAANeDk5hbm4hLp6S7NjmyoyQoIDkxJjy4syc9KjIsMCupuIJHWWVGaGyQpwMDAzMzEwMSIBRiFdAhIdXgp+nLT8+0ss70FZFgI0pwt+svSqEgYEhPchyTlNUU5KDlBAfAyMTKyuSZkZGRiEePk5mZl0dpeUJAQtctWpiDCR52EN97eKDbaREeZtLompjndtDzbsyXJUlBBkZGJgYGWGaGRgFuLn52JkMDQ1zzCz7ch2XZ5hEWynyCqoE+7rvXJSwdWZqkrfOqgaPDaXuIuzMfFwcSJoZGSX4eaX4uV2tbcJcQ/dubXp8NHVZnKKzrPbkopj2stAbO/tKU+0i4oOm1nkG2UgL8QkjvA3SLCgsISioqaabFR58flnNsZW2S+vVkqy1gqws/EIjb+9tTy4OKEo02txksiTZ2khdjoGRGaGZi42dl50l1Elv89SiOUXex5Zp9hYYxTgbqmkaJ5WnTF1bN6075Hij8dlE8Q3pzrGe1gwMcM0MDKysbGrSwlUZNjNaA0pDzFqSNHuilPvjdFSVrEsLY08c6NwxPWnf/ILTKQZzk5wM9SyYmFkQmuU1VO0stSPtdPpClevi7dxtvWYnaa/NUs9wUo+zV93e4nNtV+fDZfkzYszUlBRlpBSZmZGcraavaqEn56st2RasmOyqa2ftnOVjMCtFu85bLthCvrM2dM/CyknR+v46wnomRrJKaozw0AbpZ2JkYWZkAYuwsTAyMjKysLCws7GwMDKysTAxc7CxsbIwMzKwMTKwMjMxs7BCtAEAxCCTMp2DoxEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/00a528532d3dbd1dee8848276fae0642/a331c/2023-11-19-cover.png" srcset="/static/00a528532d3dbd1dee8848276fae0642/36ca5/2023-11-19-cover.png 200w, /static/00a528532d3dbd1dee8848276fae0642/a3397/2023-11-19-cover.png 400w, /static/00a528532d3dbd1dee8848276fae0642/a331c/2023-11-19-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>We just introduced support plans for Marten, and here’s why.</strong></p> <p>We’re really committed to providing tools that are accessible, safe and bug-free. We spend hours daily talking with over 800 users on <a href="https://discord.com/invite/WMxrvegf8H">our Discord</a>. I think that we’re doing a decent job on that for free, and we still will.</p> <p><strong>Why would you want to have a support plan?</strong></p> <p>Having it is not only about getting top-notch support (which we will) but about having a notion of <em>“I got you covered”</em>, and about building the trust that we won’t disappear and won’t go anywhere, reducing the <a href="https://en.wikipedia.org/wiki/Bus_factor">bus factor</a>. For the enterprise and critical systems, that is extremely important.</p> <p>For many companies, no paid support means a hobby project, which we’re not. We started our <a href="https://github.com/sponsors/JasperFx">GitHub Sponsors</a> some time ago. We’re already getting some income from our great users, but for many companies, sponsoring is hard to deal with, as procurement processes are not adjusted to that. Companies need invoices, and we fully understand that. That’s where official support can benefit both sides, smoothening the process.</p> <p><strong>Paid support is also about getting higher priority in resolution,</strong> building relationships and solving stuff you typically don’t want to solve through public issues or the Discord channel.</p> <p><strong>Last but not least, it’s also “help me, help you”.</strong> Marten grew big enough that we need to make our work sustainable. We tried many ways:</p> <ul> <li>joined the .NET Foundation (and left because it gives no help),</li> <li>set up GitHub sponsors,</li> <li>left our 9 to 5 jobs and ran <a href="/en/training/">consulting</a> for more flexible time and focus.</li> </ul> <p>That helped, but we need consulting, and workshops are demanding and time-consuming. We like doing that and helping people this way, but we also need more dedicated time to deliver more value to you.</p> <p><strong>Most of the comments we get are that our tools <em>just work and do the work</em>.</strong> Which, to me, is the best compliment we can get as we’re really trying hard to help our users focus and deliver business value.</p> <p><strong>Still, that also means that all primary and simple features are already delivered, and the one we have left is complex, time-consuming or both.</strong> That means that we need to get a longer span of focus purely on them to do it right. Think blue-green projection rebuilds, better DevOps tooling, observability, UIs, dashboards, etc.</p> <p><strong>We have a general sustainability issue in the OSS world.</strong> Let’s try a simple exercise; try ask yourself the following questions:</p> <ul> <li>What is your strategy if such a case happens for your most crucial library?</li> <li>Do you have a dependency management and review strategy?</li> <li>What are you doing to minimise the bus factor for your favourite libraries?</li> </ul> <p>This exercise is just to show that it’s both sides’ relationship.</p> <p><strong>We aim to be as fair as possible to make it smooth for you.</strong> We don’t want to change the license model; we understand that we need to build products, deliver you value and run a regular business-to-business relationship to make it sustainable for both sides.</p> <p><strong>If you’d like to learn more, check our <a href="https://www.jasperfx.net/support-plans/?utm_source=oskar_blog">support plans page</a> and <a href="mailto:[email protected]">feel free to reach me</a>. We’re here to help you build the best products.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[GDPR for busy developers]]>https://event-driven.io/en/gdpr_for_busy_developers/https://event-driven.io/en/gdpr_for_busy_developers/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/780b76d337c6f23e92bcab631deda495/a331c/2023-11-10-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACqklEQVQozwGfAmD9AICAsJ+gylRYs0BApFRRm1FSplBPmVlYmN3h6ubq8eru8/T2+P///26MpjRegmuIoneSqn2XrmqHogg2ZABMS5Pe2+GLfG5sXntraKh5fL9jY6g4OogjRpEoT5wuVJ82WaA/XZwfRHkaQ3AxV344XoI2XoNNb5ARQ20ASkmYwMC7/+c7kXNeXVmkYmGwQDyTLSp+CzGLBTSVAjKTACqLACF7ASFuAB1gAyNfmaq/rb3MfJKqU3SSAFlZmDk4iri32ExNqj4+jC42kh4qihMngzZYqVZ4vFh4vElrswo1jwQpeQAbYQUbXM3T4Pv8/fL7/u/1+gC2udKen8PDxdzAxNgGJXQVMmktSW42WJc7XZchSZgcRJUaQ5MTPIocPIAePno5VorZ3eX09/jovbzr2NoA8fX49/v7/f/+4ebpAiRuPU5nXGlvcYKeZHePDjiQGUGYVWVlRldjorvi793g78jH////8srJyREE4pWQAObp8OTo7+ns8n+VvgAleAgyiAs3kgAshwUzkxxIqgMylB1Ekw88j6KTrO9rOtAsEN91T9lQIs4vGfHb2gDp7fPq7fPr7/M/XaEkLIAaNocINosNOIsPPJUqV8IINZIHM5QAIod4bZDnRh3IEgDRMwDZXQjRHgDhencA7O7y6u3z7vT3VW2puUV0akuAK1eeNlSUGEKdeJfrxs/fvcjdsr7V097r/OHh8cfE4HZw1UIu2VVL89DNAM/d7Nrg6O/v71t6shcjeRg2hwc3jQApgQMwm3ye+vj5+fT2+fX4+vT19/n8/Pr///Hx8uvn7Ofj5ev2+gDa5PHe4uzz9PJphLcAIoIBL4oVPIped6aIns3Bz/Ps8Pbs8Pft8vfu8fbr7PHo6e7t8fXv9Pfu8fbq7PFJ1GyLqAP94gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/780b76d337c6f23e92bcab631deda495/a331c/2023-11-10-cover.png" srcset="/static/780b76d337c6f23e92bcab631deda495/36ca5/2023-11-10-cover.png 200w, /static/780b76d337c6f23e92bcab631deda495/a3397/2023-11-10-cover.png 400w, /static/780b76d337c6f23e92bcab631deda495/a331c/2023-11-10-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>This year, we had the fifth anniversary of the Developers’ Doomsday. In 2018, on the 25th of May, the European <a href="https://gdpr-info.eu/">General Data Protection Regulation</a> was made applicable.</strong> And it escalated!</p> <p>Right now, in almost all parts of the world, there are similar laws like the <a href="https://oag.ca.gov/privacy/ccpa">California Consumer Privacy Act</a>; <a href="https://www.priv.gc.ca/en/privacy-topics/privacy-laws-in-canada/the-personal-information-protection-and-electronic-documents-act-pipeda/">Canada has their own</a>, even <a href="https://personalinformationprotectionlaw.com/">China has it</a>.</p> <h1 id="why-do-we-need-all-those-regulations" style="position:relative;"><a href="#why-do-we-need-all-those-regulations" aria-label="why do we need all those regulations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Why do we need all those regulations?</h1> <p>Of course, bloody hackers, right? They are making everything hard. They do everything to destroy our software, steal users’ data and break our <a href="https://en.wikipedia.org/wiki/Log4Shell">favourite logging tooling</a>. That, of course, happens, but they’re not the biggest reason.</p> <p><strong>The biggest reason is us. We are the baddies.</strong></p> <p><strong>Would you be able to answer the following questions:</strong></p> <ul> <li>What data do you keep in your systems?</li> <li>How the data is distributed between services?</li> <li>What’s the lifetime of the data?</li> <li>What data categories do you have?</li> <li>What information do you export and share?</li> </ul> <p>Even for non-user data, most of us would have a challenge answering that, not even speaking about having policies. Yes, we don’t care much about data governance and privacy practices. The reality is we keep data until application logic removes it. We’re keeping all data because it may be useful in future, just in case. We’re starting to notice that when our database is overloaded or we don’t have space for backups.</p> <p><strong>As we were not motivated enough to care about users’ data, someone had to force that on us.</strong> Now, we’re forced to let our users regain control of their data. And that’s for good, not only from the fairness perspective but also for our system design. Let’s discuss why.</p> <p>Before we go, a disclaimer. Googling doesn’t make us lawyers; even ChatGPTing doesn’t. I’m not a lawyer; whatever you read here, consult with your Chief Security Officer, as those rules are highly contextual to your business domain and other laws and practices you do. You don’t have CSO? You should.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b4b37761d29acd608e70ed17ed8b216c/a331c/2023-11-10-02.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACQ0lEQVQozzWQTUhUURTHX9to0cqNQdCiwHLVqoVSEEVfFMGIFGRUoMw2FUVTyKBwYxCVmCNqJmjSl2GLNIKKgqJoESSkpTXvvft97zn3vvdmHJt4Ix3+HM6B8+d/+HkKYiYx5BAyE3KjMWJC5QMaEJ4PWEgFF0pIo8EK7QJmQqaZAqktV9YLmQmYIQKJwDzVGhxn9OfSEglDAARjwGiLkCSxgigUEHIImKECmESPCGTKcZ2KCEQXx4WCdrEBs1YslP/X+vq6NDa9VI5KJDz1e0xZpixVlkmrMPq26D8afTrVPzA6ODUx825y+PlkV8/Y1eu37s5++rqsbUIlMu2YRCbAE9qJSiyT6NbK83PzzdVbh+qr7mRPNV7Ituzd017tPc7Wnc0cm34ym6yVqURhokq+9bh2wqRiEmypvDA9cXiT17pv+3j78UMHd57etvnIFu9+y4GmE7tzE/dKf8tMAdeWCiACvArAlJlPlLbFVwtv6mp2nayt6jhTf7H3WkPjuf21O7JHa85fbpt6/dFA7FNNBFBeMROBKYBKB5t8+L7S+2Csqbe54UrbpZHZnoczHeMDme5sZuD25NvPzhZI+rYTynKJHlewYQ6YYdIt/lq5OZdrGxlsHb7ROdQ5+CzXNzPSNz7anet68f6l1AWf6VAgFUAFel+W+I9V8scnXFsBUUDU8qofSmAClHFKR2CLaIvoShqTDTpcOyqQcPAW82bFF78DJk2kMFG2oF1RYyIx4RBJiCXECmOZzpUV4/QMYqbdP+epGoqfOzaLAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 02" title="2023 11 10 02" src="/static/b4b37761d29acd608e70ed17ed8b216c/a331c/2023-11-10-02.png" srcset="/static/b4b37761d29acd608e70ed17ed8b216c/36ca5/2023-11-10-02.png 200w, /static/b4b37761d29acd608e70ed17ed8b216c/a3397/2023-11-10-02.png 400w, /static/b4b37761d29acd608e70ed17ed8b216c/a331c/2023-11-10-02.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="the-law-to-be-forgotten" style="position:relative;"><a href="#the-law-to-be-forgotten" aria-label="the law to be forgotten permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The law to be forgotten</h2> <p>The most known part of the GDPR is the <em>law to be forgotten</em> or <em>right of erasure</em>. Our user can request the removal of their data from our system, and we’re obliged to do it.</p> <p>We need to give users an option to request data removal. This can be some fort on UI, email channel, etc. Once we get such a request, we can either do a batch removal from our database or schedule it. Usually, the former method is preferred, as it may take some time, and regulations don’t oblige us to do it instantaneously.</p> <p>No matter which way we choose, we need to clean all privacy-related data like names, social numbers, emails, addresses, IPs, etc. We can do it in two general ways:</p> <ul> <li>deleting physically all rows in tables,</li> <li>anonymising them (taking precise information like first name, last name, and address and replacing them with randomised generic ones).</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6745fbf37d4d52964ae9c7773ab48050/a331c/2023-11-10-03.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB6klEQVQozy2SWWpVQRCGz0p0DeLWxOe8iuAKNNONQjAYxJBthEDCHc7UfXoeqscz3MQX6XuFn3rp+rv++qjKp8W4rCH3mD88Pp2er37++v3t7PLT5y9fTy9vbu/OLq5W368vVj9Oz6+ub26ZBOuzC6P1YyVNFCZqN3IdBqqYhF1PNjV63nYNYnXHNs2wafFmhzY17okU9tAPybhcKcjGl28gLZJKVPe7Gm0bDHHyee/yizKBYSK1d+nFpb0Jk3ZZ2eKvjB+L3Gj9pGyQEggVNe658caVJ8Z1t16TpmESjJ/KpDBZlw2kCsIEYbKlzhBnSIt2Y9/h5223a8nTumkbPLQ9R4M2oTSESblRuzKvKrZ4UJpsnBQkLhSjlHMuNAipB4QYZSUUZMQd13GLzboVyqZKHIAdJW0qYtjJQbNuIEzQPjjiLBacExWaQXfEMBUoh2JWMBYAkIwfIcwaUofotsF1R5gOaFf393+6+zuKmQkz5iBMcnH2YTrE9ulgzsJGBZkJYEyIoVGkoToyhF7fvlnev2NSCpO4DiUaJH2k/dDaZlCUKxtGiLM0QUjlJLa0NXESPYKPH8zJiRAa0uzif7oakoJU7WjE3BJhSpi8d2kpZ1CWz0fyYv6r5lfwo8+LS4vPi897n2bjp3+RhEzJVJGg3QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 03" title="2023 11 10 03" src="/static/6745fbf37d4d52964ae9c7773ab48050/a331c/2023-11-10-03.png" srcset="/static/6745fbf37d4d52964ae9c7773ab48050/36ca5/2023-11-10-03.png 200w, /static/6745fbf37d4d52964ae9c7773ab48050/a3397/2023-11-10-03.png 400w, /static/6745fbf37d4d52964ae9c7773ab48050/a331c/2023-11-10-03.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>It’s worth noting that we don’t need to remove all data.</strong> We need to remove only those allowing us to deduce their identity. And that’s highly contextual and tricky. For instance, typically, we can leave information like gender as it’s not precise, but not always. If we have a class in tech school, and there’s only a single woman there, then gender in this context is <em><a href="https://en.wikipedia.org/wiki/Personal_data">Personally Identifiable Information (PII)</a></em>, as we can identify a specific woman in this context.</p> <p><strong>Also, we cannot always remove users’ data.</strong> There may be higher-priority laws that would force us to keep them (e.g., medical history in hospitals, tax history, etc.). To make it even trickier, we need to do our best to detect that person requested data removal from our system when we’re merging companies or buying user data (a common practice in, e.g., recruitment companies). That can be done by keeping some irreversible hashes and comparing them with incoming data.</p> <p><strong>One more thing to add: never keep PII data in your application logs.</strong> Like, really, never. It’s a pathology. Not only they won’t help you track the issue, but you’ll also have a hard time cleaning them on request. They’re a big append-only blog and don’t provide such precise removal functionality. But okay, let’s say we were sane enough not to do it.</p> <p>We get the removal request; we run SQL script to remove or anonymise data and run it. Poof! The user’s data from tables disappeared. That’s cool, but is it actually?</p> <p><strong>It’s surprisingly hard to tell what one means by <em>“deleting data”</em>.</strong> Let’s take relational databases as an example. Do you know that they also <a href="/en/relational_databases_are_event_stores/">work as append-only logs internally</a>? When you run a delete statement (or any other like insert or update), a new record is appended to the structure called <em><a href="https://www.postgresql.org/docs/current/wal-intro.html">Write-Ahead Log</a></em> or <em><a href="https://learn.microsoft.com/en-us/sql/relational-databases/logs/the-transaction-log-sql-server">Transaction Log</a></em>. When you commit the transaction, the database updates the tables based on the data from the log. Once that’s done, it can clean the used log records. Yet, you won’t know when it does it, as the Write-Ahead Log is also used for disaster recovery, etc. The same applies to most of the other database types.</p> <p>What’s more, if you’re using Change Detection Capture features like <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">Postgres Logical Replication</a>, then log entries may not be ever removed at all.</p> <p><strong>So you need to ensure that Write-Ahead Log entries have a retention policy with your Privacy laws (for GDPR, it’s a maximum of 30 days) for tables that contain user data.</strong></p> <h2 id="what-about-backups" style="position:relative;"><a href="#what-about-backups" aria-label="what about backups permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What about backups?</h2> <p>The (not so) simple removal from tables is getting much more complicated if we add backups into the equation. You’re not doing backups? Then let me tell you that there are two types of people: those who are doing backups and those who will be doing them.</p> <p><strong>If we remove or anonymise the data and restore the backup, we immediately get it back.</strong> In theory, we could remove/anonymise data back after running restore; that already sounds hacky, but maybe it could be a tradeoff? Not quite.</p> <p><strong>Besides the application data, we need to store information about users’ consents and requests for removal.</strong> If we keep it in the same database, the recent consents or requests may also be brought back to the previous state, where we were allowed to keep their data. So, we might not even know whether we should anonymise or delete it again after restoring backup.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b163bf2d228a3b26632d900edf836d25/a331c/2023-11-10-04.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB+0lEQVQozzXSW2sTQRQA4P3xIhTxB1hN0zZtaWhotVSoICj44lvx0qbJZi+zczszZy47O7MmeZNNFM7D4XAu8HEy3621i9rFioiHH7+PjidX1x9G49ODV6/fHp3Obu7GJ2eT88uTycXR8dl0dsPAoo+2TcanDDAABvRJYtsIZGDzgj7n5GlRrSqeV2xZ0lXNnvP6Oa+rBpTpwHToIrqYgemUjcoOx9EnbaPxvQ1rHze2W6PrteuVS7vK1nUbMB3XnkkLGDKhLONACOdCcbDG99rtFvtEJVYNKypKqBTg5k9LJg3lihDWEEa5yrSymtSeM8eokOji1oS1DWsXtwJQK7DWAABoD1wDtoqyIGWQQjKRKdMSKhomGybBBEStWKkVGINCYUXYPtBH161t6BsBDYeGScp1BjgADGw7CVEv+OonWz0ySveVHUoCDFIH+b9N7aSyvZayHfpk2x5ByHrBmxLbHl3UA2TSLqFLykYc8ujjxrW9djEzvtNumN+vzIuGCUD776bAFt2AL3SLLkkMvtvmFefSapeyh8KuGkU5+LSZ59WLlwdfvn2fXr1/8258e3c/Gk9Ozi+vbz9eTGeHo+PD0Xg6u/n0+SsViC5lCxaIMFTg8Gq2m+fVsqR5SR8XxbKk87xeFqQkvKjYr3k+X5RPy9KGP23coO//ApFORgyk7cFFAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 04" title="2023 11 10 04" src="/static/b163bf2d228a3b26632d900edf836d25/a331c/2023-11-10-04.png" srcset="/static/b163bf2d228a3b26632d900edf836d25/36ca5/2023-11-10-04.png 200w, /static/b163bf2d228a3b26632d900edf836d25/a3397/2023-11-10-04.png 400w, /static/b163bf2d228a3b26632d900edf836d25/a331c/2023-11-10-04.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>What’s more, someone with access to backups can restore it to another place and read the data.</p> <p>Defining our data policy, so:</p> <ul> <li>what we store,</li> <li>where we keep it,</li> <li>how do we backup and restore data,</li> <li>how long we keep data (including backups),</li> </ul> <p>is essential for defining the proper Data Governance process in general, not only for GDPR.</p> <p><strong>In the GDPR context, my recommendation is:</strong></p> <ul> <li>keep application data and user consents/requests separated,</li> <li>use the secured and fault-tolerant store for consents and requests,</li> <li>do backups regularly (especially for the consents),</li> <li>define the maximum length of the backups aligned with your Privacy laws (for GDPR, it’s a maximum of 30 days); don’t keep them longer. By that, you don’t need to clean up data from backups,</li> <li>define your disaster recovery strategy, including the removal/anonymisation process as part of it.</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6e83e80fae0d3592016455417da17cb4/a331c/2023-11-10-05.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByUlEQVQozy1SXW/bMAz0//9BA7ahRdcV+3roijRt48ZN4o9YpESREiU77tsgdwAfiMPxCNxdJXpxnMApuIgUOU4cpxUJSIo+cZxjWjhO6BVIwQXHyiGT5ApcNC6gLzyzHtiyRCBFUqAIFJ1kKEi0nIDUuGi9Ok4VeKU4sy5OspUijxQsq4/zGcVydjIBBesjhdmFyXIqHBctaSW67Pbtrmk5vSOMcHoxp9qcdhbMYBUAsatXpIbhRDKF/C5p8WGyXivH6eef+293vwypM/3YbMzx2TQPcG5HyggGDltzfDJvj9C+DkA/ft8/bHfNoSeZKnDh6ubu6ub7Gb2D0Rx3cKrN8cWa3viEiNjusX3FUw3dm5Py6ev17bEzllNVN92Xq9tPn683Lw2M56HZDs1Tv9+c+7YzMgxj//bcF+SxO7yupsZuJJKMXiv0yXK2nEkmkoSO0RJYcpIG8CU/J4BkwIETy4kks85eUnGbWFcD05qKFntXLSQdgEcbPtShEEoX1szUrolWfxvad9iPlkL2odSjyIfs48Q6U5z82geSgnwMFatLYFU9hHb0/UgcZ8kLp0X+z4V1Zr2EtMT8HtIiemG9SLrIujuZ/gFPy1nYOfBZbAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 05" title="2023 11 10 05" src="/static/6e83e80fae0d3592016455417da17cb4/a331c/2023-11-10-05.png" srcset="/static/6e83e80fae0d3592016455417da17cb4/36ca5/2023-11-10-05.png 200w, /static/6e83e80fae0d3592016455417da17cb4/a3397/2023-11-10-05.png 400w, /static/6e83e80fae0d3592016455417da17cb4/a331c/2023-11-10-05.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="user-rights" style="position:relative;"><a href="#user-rights" aria-label="user rights permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>User Rights</h2> <p>Most people think that GDPR equals <em>removing data</em>. And that’s a highly flattened and oversimplified view.</p> <p><strong>GDPR and other privacy regulations are about caring for users and their rights. Empathy and not being evil.</strong> Here’s the full list:</p> <ul> <li><strong>The right of erasure</strong>,</li> <li><strong>The right to be informed</strong> (about what data we keep and what we do with it),</li> <li><strong>The right of access</strong> (so showing users all data we keep about them),</li> <li><strong>The right to rectification</strong> (we should allow users to edit their data),</li> <li><strong>The right to restrict</strong> (users should be able to tell which data we can use),</li> <li><strong>The right to data portability</strong> (we should allow users to export their data. Yes, even move them to our competition).</li> <li><strong>The right to object</strong> (We should allow them to object to using their data for specific needs, e.g., marketing or sharing externally). No, annoying cookie popups are not a solution),</li> <li><strong>The rights regarding automated decision-making and profiling</strong> (user should be able to object to using their data in, e.g. AI-based credit scoring, etc.)</li> </ul> <p>By the way, in GDPR terms, we are not talking about users but about <em>Data Subjects</em>. We also have terms like <em>Data processors</em>, which are we, our company, running software systems and processing users’ data. We also have <em>Data Controllers</em> who are either storage providers or, again, us if we’re running on-premise and handling everything on our own.</p> <p>The biggest responsibility lies on us, as we know what data we store and how we use it. We cannot just offload it to <em>Data Controllers</em>. They must run everything with the state of the art, but we still need to ensure that we select the proper data provider and align our usage practices with their recommendations. As I wrote in <a href="/en/form_a_wall/">Form a wall! And other concerns about security</a>, it’s not enough to <em>just use Cloud</em>.</p> <p>Yeah, being a man in the middle is always the worst position.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3ae111f51ff7ec2a569c70575c9bb18e/a331c/2023-11-10-08.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACDUlEQVQozx2SSWsUURSF65fobxB/m66zF8GFCK4cUCRiQJw2AWdJlOCQmO50dQ2v3jzdN9bQbUC0srmczTkf595b+DQZl7XrG8R/nSx8mt6+/3Tj5q37Dx/vPnv+5OnevQePbt+5+/N4iajCzCiIJ8cLqT3EsZAmChO1y1wHTDUVtqWyrPGibGvE1y07q7pF2a4b2mJFuHVperG7d7oofd4UyibjexsGSBtJFa7QatWeVQjC4PPGpa2ykWMiZtRk/ZA2fz/ufzh49zkMfwoTRuMHKq20QWnHCMMdWWPEjTduDmVct8slqSsmwbje99vTRXl08BXiVIB12roWYSa0gUjJLFCLf6/qsiEny7quEa4bjrA2c0+IIzcRc6shFwJVSirtsvU9xEGYwLhklDLOhQYhFUYdo0yZICEjDkzHsjOrViqbCq6CMFHaJG1U80yCYSexZg0mTNA2OOJsxzknKlRYN8QwFQiH2axdr1xWkI3vIQwacoPoqurWLWE6oFXZvn7ZvHlFMTNh7BgIG63vrZvXXFif9IVZ2KggMwGMCY4rRSqqI2vR9vKl6eoVJqUwiWuvICPMu44qyMWP2lZYUa5s6CGO0gQhlJOdZbWJg2gQXL9md3aE0JBGFwaXxi+HR4ffvs/kFY0dByKMi+PFYSdho5jLZ0iTdr0cz9V4Dn7waYI0hX7LlOMK/n/YPwCiSshRwKJ6AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 08" title="2023 11 10 08" src="/static/3ae111f51ff7ec2a569c70575c9bb18e/a331c/2023-11-10-08.png" srcset="/static/3ae111f51ff7ec2a569c70575c9bb18e/36ca5/2023-11-10-08.png 200w, /static/3ae111f51ff7ec2a569c70575c9bb18e/a3397/2023-11-10-08.png 400w, /static/3ae111f51ff7ec2a569c70575c9bb18e/a331c/2023-11-10-08.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="the-right-to-be-informed" style="position:relative;"><a href="#the-right-to-be-informed" aria-label="the right to be informed permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The right to be informed</h2> <p>Could you think for a moment about the user data you have in your system? How many tables/collections do you see? If only a few, then defining the removal strategy can be not that hard. But usually, data is spread all over if we don’t care about governance from the beginning.</p> <p>This impacts not only the removal but also other user rights. We need to be able to inform users about what data we have and export them.</p> <p>Some can say that:</p> <blockquote> <p>Yeah, no worries, we will just make some registry, we will write the documentation, and we will precisely know what we are doing with our data</p> </blockquote> <p>If you also think that’s easy, see how much data can be considered PII:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/10ed2b6af448259b5f2dba50c3d21f70/a331c/2023-11-10-06.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACU0lEQVQozyXPyW7TUACFYT8iS3YsWPIG7FjConQg0KoqdKItldoKVRVj5qROHMf2je14iH09xNd2PE9J3EYgVCp9+ndncbBas7O2sXV2fnlydrG+WTn7crF/+Pn99u7+4cna+ru3G5XDo9Prm+9ble0PO3tHJ+e7ewe7Hw+IAZ0vVphpByTNkzTPCQrFCtRoPGQ4wMtgLIuKAU13olkynCoaUjQkyPporHCSqpmO6ycYmkUDmq01u/UW3mjj3d6wN2BuCeq2Tw1ojhVUTlAAJ4sT3Y8LGZqAEwEv8YIKDRsLk7lmuoCXOVFRDVubutC0oYEU3YKmM9EsRbdVw4amY7mRIGv9Iej2hgQJNNPB3CClwLha77RxkgK8qluyOjWQJ0PTnsXZfJXN79PiPs6WQbIwkKfoaKLb0LBN5GGun454qd3td3skTlCqgbSp88hEMxPNoGlbThCmCz/Kiru/nWr1cm/HC3MvKjDbS6iR0Gjh7dvBbZ+iwLhPMrwERcWQoaVbnqIh3fK8KHeDNCxW+M9f1xuvQz+ahTmGvIQC40anN6A5hhUBJ1GAZ1iRHgljSdMtz/HTOC+RF6uGM4uXNZw7/dqYBmUQF5jjRcRwdPOj+rveaXb6re4jAidoYsjW2/iAYkXFUHTn+Pi81gWfrhr7V82bNmd7McbAkJUMwAma6ehTR9GR7cVJcRfnZZwv47x8eBsXUV6SfXJzrfLsxctvLcawIz/MMMlKDBRMHT9MFw+DrIyy5aPwf5O8TIu7OFvOyz8ytJ48ff7qTYXhpHS++gewHiaRCT+thgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 06" title="2023 11 10 06" src="/static/10ed2b6af448259b5f2dba50c3d21f70/a331c/2023-11-10-06.png" srcset="/static/10ed2b6af448259b5f2dba50c3d21f70/36ca5/2023-11-10-06.png 200w, /static/10ed2b6af448259b5f2dba50c3d21f70/a3397/2023-11-10-06.png 400w, /static/10ed2b6af448259b5f2dba50c3d21f70/a331c/2023-11-10-06.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span> <span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6bc894ad77aa9e8a150ec985098f5d68/a331c/2023-11-10-07.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACMElEQVQozzWRS2/TUBCF/Q/ZsmPDjn/AjiVIlELboKhqaSFtaaW2QhUgBIUWFPURp7ETx4nd+Bm/r+/L19d2VIGQE5DOZkaj+c6ZEX6ct5eWV3f3j3b3D5dX1t4fHG5utdYazTfbO89frCy/arxt7Z18+rLaaL5ubrzbOVjf2F7f3L7q9FhxJ1huJEqqNNBkRROl4XVXHo4muulphmvY/sTyNWM6Nhzd9CaWP7q1++pE0QzLDSNAhGmQyorW649sLw4BcfwkhgzREmUlziqUlZCWKeGQlpAUuunKylhWtOHIMJ1ACBKs6pYoDQ078EIICQeYAcSSWnmCGMB5gnKA8ghQVTOvunL7snstypYbCmGCldGkczOQBuMbWTUcf07jtUiRYo5ogWpsmWLu+MnE9m9t33QC10+EEBBVM4IYUj5DWQHmkLlPjrLFCl6XhAOU5bM/7dPTo43mwpfgBumNrHZldaiZ1jQGmMeQxTCLUhqlNAQ0SrOo7rA4zWB+d/H128nLpwigGDLBi7Cs6BfXPbGnOH5Se66xRUryRXKA85QUQUIMJ4xx+f1C2ftwNk0rgHMhjGCvP7oU5U5PcYMUoHxOZjX2H5kCzC03brX2T9v9reOzrePzj78GQYKFjhZJqtFXxrrh1nOEJ4gtMkNaQFLM75/jvOpeiStLaw8ePf78U3ICDGAmaB61fTgNAKQFZtV/zTCr5q8uCatoPsNZyavfuundu//wybOGrGiU3/0Fe6w4p3oOpsMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 07" title="2023 11 10 07" src="/static/6bc894ad77aa9e8a150ec985098f5d68/a331c/2023-11-10-07.png" srcset="/static/6bc894ad77aa9e8a150ec985098f5d68/36ca5/2023-11-10-07.png 200w, /static/6bc894ad77aa9e8a150ec985098f5d68/a3397/2023-11-10-07.png 400w, /static/6bc894ad77aa9e8a150ec985098f5d68/a331c/2023-11-10-07.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Remember that such a registry needs to be actively maintained and reviewed.</strong> We know how hard it is to <a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">keep documentation up to date</a>. Usually, trying to audit the existing system for GDPR data is like trying to find <a href="https://www.google.com/search?q=where%27s+wally&#x26;tbm=isch">where’s Wally</a>.</p> <p>To make it manageable, we should limit the GDPR data’s usage. Instead of spreading it in multiple tables, keep it in the same place.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/bf639088e52365d8fcecb950e28b0154/a331c/2023-11-10-09.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB+UlEQVQozx2RzWsUQRDF5/8/iPgdUIxrNIIgeFUJhkQTDSEfbHDXzGZ3dqZ7prq6urqru2c2eJHZSx0ej3qP9ytYBnSCnC+ubi6vpy92Xr7anUz29ncn73ff7j96+vz1m73Juw9Pnu08ePj44PDH5fW0NY5DbzkVHYbWBENRgVs1sGrg72J9M1/OympeVuVK3y6bRaXu1t3stiqXqlJGGzYk6GIBJNb3FAby2foMJEjifGLpgYLlSD4ZJ0DBycCysTx6AIOxUjjZzMrqajp3MhhOxgZ0AZ0Qx1oBIBNHYrFOOgzImeOG44Z8NhSL2P87Pbs4Pjnz6Z5Cjy5Qt0a9ZFRagza4auuqa4DIclJAP3+dX01n5V1tORfkc9PZpkUKmULfofNqHmGRzZ1WqmqbhV4su6XC1nK0Pp38Pv96cLhct8bFosUALgLFFkNnBdB7U0erIummaZsOFLYKO7BWgzMuaRPW2lpOQFIAJaA47sTJ+ry9iXxyIa8VgGXyiTghCVA0LlpOTvpRcamwLGab3FnprJjxl5itTwGPvWjUW5RttdBtC5rRKcXFwt6uodEwBoZsXHQyTP+UHz99Pjo5+/Lt++HxKY7AMvnsxl1GomZbtpg3odJUa3Sh57hxsvHpXhu3qqHWWGtsWnQhh7hhGUbUcRhpSY+c/wNJMlDfPfR1DgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 09" title="2023 11 10 09" src="/static/bf639088e52365d8fcecb950e28b0154/a331c/2023-11-10-09.png" srcset="/static/bf639088e52365d8fcecb950e28b0154/36ca5/2023-11-10-09.png 200w, /static/bf639088e52365d8fcecb950e28b0154/a3397/2023-11-10-09.png 400w, /static/bf639088e52365d8fcecb950e28b0154/a331c/2023-11-10-09.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>You can consider keeping it in the separate database schema.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d7dd6d8d9082a35ad85c1680bf523438/a331c/2023-11-10-10.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACB0lEQVQozz2RXWvUQBSG8/8vRLFYCkp1adVLQSiCUrS0trrUurLd5nMmk0zm+8xkZpLNijeaFoTn4lyclxeeN3F+0jZoN96sbn/+Wu8fPH9xuFgcvTlcHL9cvH6yt3/46mhx/Hbv2cGjx09Pv1z8XK25ss6Pxg2J0J6rXkLspK2prKksqmaT4Qy1BaKoEQXuEGGoFWlBStyRTjHlFARtYyIhmn4LfoJ+NP0ojBfaCe2k7lsmO6GZhE4YJp31k4s7048SgtRemZDYMKUlWa0zGybdj5TrCmGMCa6bvMQVblDdorrFhDJpjRtd2Lm4s/2oICRh++dqeXN+ueyHHfiJC8nRWtZ3gqRVVWU1Ktoqa0pMW2WHTsLFt+vVOitRa9yYQD9SYSjX4EcIU8eEwBtFUkPzqiw2OE9JviFZ2dQKAvTj5ffrj6dnuOEK4ixMQZQQufYSIu0YQ7e6yRRJ8zzboDxtyjtS5DWmHKQdmOobZowbJIREwiDncNBuAL/l2hFCCK4IIWleZQjn9QxqOmG8stG4wYYtuGG2bWxQdm4WJggTtB2ECVx7rn3LTCeduL//I7QXJigTZmE/KlM2smXS9AP4Udlow3R+uXz3/uRqeXPy4dPns69UwNx2/wB+XlRBkCYkGfWEQcu1DVsXdw90AkgnG6YJlQ8uffxt/WTD5OK8tgvTP9t/AbgCSjomEltQAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 10" title="2023 11 10 10" src="/static/d7dd6d8d9082a35ad85c1680bf523438/a331c/2023-11-10-10.png" srcset="/static/d7dd6d8d9082a35ad85c1680bf523438/36ca5/2023-11-10-10.png 200w, /static/d7dd6d8d9082a35ad85c1680bf523438/a3397/2023-11-10-10.png 400w, /static/d7dd6d8d9082a35ad85c1680bf523438/a331c/2023-11-10-10.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Or even a separate database.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/aef95ae7df5e4147f88af2699b9363bd/a331c/2023-11-10-11.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACEElEQVQozx2S2WoUURCG+zm99RG8Eq98AVEMakCJGRL1HYRAwJCYaGYm6emZ3vc+W529z3SrEOQEfoq6qOKDryqQZmLCMumW683qLvxw+PHT0fHi5OvR4vTz4subg/dHxyeL028H7w5fvX57dn6xWoeICqkdyDHATCOmCLc9EVXPqo4meRslZZw3adHlDc7KvqiHsiFx1mRVX/d0oJJyw4QNCDeg9lxPXDkQilLKGONCKTtjMEw6Km1HeUeBSiPMBMoRbjBTlNtAmHmX1etNIsyEis2QLvts3adLQmhHJGIqqpK7PFrn0Srb9Ewo+1fYmStHuQnM/t/VzfLH5Y1HNRmuElzFqNpRxhDTBHTSlts6i6o0boqyQ5fXt+twlxUtSBdw5ToMHQKuRtykqIxQ4cMYxaCp0LsmD8s4qtJtnfaMX90sv5+dlw2iwnphhFvCLQLdtXVXJl0Zt2UyYGh66InM2nZbFlGZb8tiYAox3QwAcsTcBISPj8sGlPM+9MSUA2mFsi3iGLxOJkbKRyp8QI7C7Lm03jYIQ4UntwjawaMQQl12XyVh0xPENGaGcIvBPPbaVzAUjBd2m0PakHYgdUd+r8JVGGOE7q/Pnz19cnlxYecHJi2TI6iRaye08xd9VE3ABHGn6wFaxKSZhNlLO6tx2tzfvXzx/Of1L/fnQeq9drOys/ADk7STtLP/S+n+A0erSKaQpnA8AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 11" title="2023 11 10 11" src="/static/aef95ae7df5e4147f88af2699b9363bd/a331c/2023-11-10-11.png" srcset="/static/aef95ae7df5e4147f88af2699b9363bd/36ca5/2023-11-10-11.png 200w, /static/aef95ae7df5e4147f88af2699b9363bd/a3397/2023-11-10-11.png 400w, /static/aef95ae7df5e4147f88af2699b9363bd/a331c/2023-11-10-11.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Centralising the PII storage is not always the best choice.</strong> As I mentioned, whether or not some data is personally identifiable is always contextual. Also, for some data (e.g., medical or financial), we may be obliged to keep them longer, and for some, shorter. That creates a challenge to create a central, generic PII Data module. Different modules will require different data lifetimes; those requirements can be contrary. We may end up with the lowest common denominator instead of the proper solution.</p> <p>Also, we’ll be facing all the <a href="https://en.wikipedia.org/wiki/Conway%27s_law">Conway’s Law</a> issues and potentially chattiness between modules and degraded performance. Caching can help, but we also need to remember to clean up the cache after a user request or set a proper Time To Leave on it. We also need to ensure a higher security setup.</p> <p><strong>I think that centralisation of user content and requests can be easier.</strong> This is more generic, and by itself, it may not even need to keep private data. You may keep in it:</p> <ul> <li>user identifier (as long it’s not email or based on other PII data),</li> <li>module name,</li> <li>consent/request type,</li> <li>consent/request value (typically Yes/No).</li> </ul> <p>Thanks to that, you could build a generic solution to record and publish information about changes to user consent and requests. Then, other modules can subscribe to those notifications and apply requests (e.g. for removal) accordingly.</p> <h2 id="sharing-pii-data" style="position:relative;"><a href="#sharing-pii-data" aria-label="sharing pii data permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Sharing PII data</h2> <p>Remember, from the data that’s keeping PII data, once the data is exposed, you’re losing control of it. If someone queries the data, they can do whatever they need. That applies also to other modules in your systems.</p> <p>Of course, we can handle that for internal modules. As mentioned, we can publish information about removing and handling data.</p> <p><strong>The basic rule of sharing data with external systems is: you don’t.</strong> Yes, I know sometimes you have to. For instance, we’re using external identity providers, trusting that they’ll handle that better than we. They generally do, but see the <a href="https://arstechnica.com/security/2023/09/hack-of-a-microsoft-corporate-account-led-to-azure-breach-by-chinese-hackers/">Azure AD breach details</a> or <a href="https://blog.cloudflare.com/how-cloudflare-mitigated-yet-another-okta-compromise/">repeating OKTA issues</a>. If such companies have issues properly handling privacy data, consider how others do it.</p> <p>If you’re sharing the data with others, at least ensure that they allow you to request the removal of the data. Check if they have certifications of common standards like <a href="https://en.wikipedia.org/wiki/System_and_Organization_Controls">SOC 2</a>, <a href="https://en.wikipedia.org/wiki/ISO/IEC_27001">ISO 27001</a> or <a href="https://en.wikipedia.org/wiki/Health_Insurance_Portability_and_Accountability_Act">HIPAA</a>. It won’t give a full guarantee, but it’ll show you that they have procedures and that you can demand to apply them and place them in contract terms.</p> <h2 id="gdpr-and-event-driven-systems" style="position:relative;"><a href="#gdpr-and-event-driven-systems" aria-label="gdpr and event driven systems permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>GDPR and Event-driven systems</h2> <p><strong>As you see, Handling privacy can be tricky, and for Event Sourcing, even more. The most significant Event Sourcing selling point is that <a href="/en/never_lose_data_with_event_sourcing/">we’re not losing data by using it</a>.</strong> Our data is an immutable sequence of events, and that sounds already unmatchable with removing data, aye?</p> <p>How to do it? I explained that in the follow up article: <a href="/en/gdpr_in_event_driven_architecture">How to deal with privacy and GDPR in Event-Sourced systems</a>.</p> <p>You can also watch:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/7NGlYgobTyY?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>We should not be scared of GDPR and other privacy regulations. GDPR is a privacy framework. It’s good that we got those regulations and those recommendations on how to work with our data. Those practices should not be new to us; we should have already been applying them.</p> <p>It doesn’t have to be all that hard to do if we try to limit the scope of users’ data we need and keep them as short as possible. If we don’t spread it across the whole system but try to group them logically, we might not have a headache knowing what to delete and how to show users what we know about them.</p> <p>Those lifecycle policies will make GDPR easier, and our systems faster and better managed, as our databases won’t be bloated, and we’ll have more data flow control.</p> <p>We should finally apply privacy by default because we’re not baddies, right?</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b3d59818fe2ada524bab32c099b4c714/a331c/2023-11-10-12.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACaklEQVQozy2Q3UsUURjG33baVdHKVvzIsLoTTTJS8qKPJcEWXNvV3fUDM6OLJLISTRPqHwi66D6QiAIpCZNACIqgBAuMqCAS8iK/dnfOmTlnzpmZMzu7sxNTwsMLLzw/ePgBYQIRnsYsJVPChO24Vs41LIeLPBcOM3NZ280VXDvvcsshPJtCFBGmUgMTHdJIS3mhhu3OPH4ajcVv3ByfnL47NX3v/oOHj57Mzjx7MTv36vnc/PzC4tLyShp7fVlhSOWQUTgiehprjuveGpsAgEAg4PcXAUDZ3v2Nrael4jLw+SVpNwCcORtSNCGrPPOPB0R0RPQU1rKOO3JtFACKiksln1/yScHKmqraI+UV1fuCVfUNTQdq66KxJNWzHqJwpDBQqIGpISvcyrtDly57cGm5v6QMAI42Nbef66ivbwwGKw4drAOAeKJPF3lEdUx0WdUBU0PxHm7ahd6BQQCQSvb4AiUe3HwimehvaW2rqqwOVtYA7Bq+el3kXFllssIyCofUjjANESPSnWw52dYZ7QlHLpwKtcf7Bycm7nT3JEPtHaFw5+GGYyNjt7lwtmUqY5ZRGGRU7kVhiibGJqfDka7ei8N9Q1fOxxKRxEBXdzLcGY10xeK9Aw3HW0bHp2zX9cpE92xjwjx7Ck9jDVMjjbT1LbS6tvHj19rKt59Ly1/ef/z85t2HhcW3sy9ff1r5jqmZwkzG3nL4uoZ/r2c2t2VMdUS4rHKZ6CoTVLeYaXOR1y3HyBaEXcjmXW7msLYj2Ju9uqn92VI2UkjVTMItlQmVCeJd839P1UyVmSo1ENExNQgXhFuECUyNv+GZt35RGQYcAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="2023 11 10 12" title="2023 11 10 12" src="/static/b3d59818fe2ada524bab32c099b4c714/a331c/2023-11-10-12.png" srcset="/static/b3d59818fe2ada524bab32c099b4c714/36ca5/2023-11-10-12.png 200w, /static/b3d59818fe2ada524bab32c099b4c714/a3397/2023-11-10-12.png 400w, /static/b3d59818fe2ada524bab32c099b4c714/a331c/2023-11-10-12.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Anti-patterns in event modelling - Clickbait event]]>https://event-driven.io/en/clickbait_event/https://event-driven.io/en/clickbait_event/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6daab209c7154b2e77a112700ebc4b82/a331c/2023-11-05-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABrklEQVQoz42SX2/SYBSH9+m88MLEO+Nn8EKvuSGYoJOB02BAJJEtZtN4x41IYkSixhvntrIWFEJLKSxQpIXR8qftY1pdMmVz/JKTNzkn53l/7znvGn/keV4QvgqFAuvr94nFYqRSKXK5HNlslifJZJCLRqMUi8WlPl9r5wG3X+xw6/YdwuEw8XicdDpNJpMhkUgQiUQIhULk8/nVgT1dR+t0UFsqut5HURRMw0Brqxx3u4xNk6ltXw48PQeDAYos02w0adQbSKLEkSCwfyhQkUSG4xOs6fyvniWgL9d1g1NVVcrlMq9fvWTjXoTU402KnwVm9T2k3edUtx4xkw/wftMudngKbMpNSqUPJB9ucOPaFW5ev0o89gCztMPbp894t3kXt/IGJ2h0L3b4r/1et8uXj2W+fnpPvfKNvibTOtrDaDeYjof/f/LZ4bqey2I+xzqZMDJHWJaN43iYxghd/8nQGGFN7NW3rLRafK/V0NQ2g74eRFWUEPYPqIoiP2o19F5/9S1blhV8C9dx8Se/mM3ptLXgAnNoYE+soHbploOCdzbhz3w558OchXOuw18iojCHYO4zIwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/6daab209c7154b2e77a112700ebc4b82/a331c/2023-11-05-cover.png" srcset="/static/6daab209c7154b2e77a112700ebc4b82/36ca5/2023-11-05-cover.png 200w, /static/6daab209c7154b2e77a112700ebc4b82/a3397/2023-11-05-cover.png 400w, /static/6daab209c7154b2e77a112700ebc4b82/a331c/2023-11-05-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <blockquote> <p><em><strong>Doctors Hate Her! Discover the Simple Trick to Lose Weight Overnight!</strong></em></p> </blockquote> <blockquote> <p><em><strong>This One Weird Trick Can Save You Hundreds on Your Energy Bills!</strong></em></p> </blockquote> <blockquote> <p><em><strong>The Secret Investment Banks Don’t Want You to Know About!</strong></em></p> </blockquote> <p>I’m sure you’ve seen such clickbaits, aye? News portals and influencers do their best to keep us clicking and uncovering that they only have a little to say. Usually, this secret advice behind the headline is common knowledge. We’re wasting time going through a gibberish, filled with words article just to find nothing we haven’t already known.</p> <p>Surprisingly, that’s also a common mistake we make when designing events. Look at that beauty:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">record</span> <span class="token class-name">AccountInformationUpdated</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> AccountId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Or that one:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">record</span> <span class="token class-name">ShipmentStatusChanged</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShipmentId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Those events are notifying us that something has happened but are not giving any context.</strong> Rarely, that can be good enough information to trigger some next step of the workflow or do some generic action like logging, but typically, we need to query the publisher. And that’s a rookie mistake that will hit us hard later.</p> <p><strong>There are multiple reasons for this mistake. Let’s go through the most common ones.</strong></p> <h2 id="living-still-in-the-synchronous-world" style="position:relative;"><a href="#living-still-in-the-synchronous-world" aria-label="living still in the synchronous world permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Living still in the synchronous world</h2> <p>Let’s say that you need to get information from your insurer. You’re trying to call the hotline, but you realise it’s only open from 9 to 5. Maybe it’s not urgent, so you decide to try again right in the morning. You’re setting an alarm for 9 AM the next day, call and… You hear to <a href="https://www.youtube.com/watch?v=htgr3pvBr-I">hold the line </a> followed by information that you’re the 24th person in the queue.</p> <p><strong>If you’re applying an event-driven approach by just sending raw notifications, then it’s like an alarm for 9 AM to make a phone call.</strong> Sure, it may help a bit, but actually, that’s the same communication you had before. To make it event-driven, you should try harder and reshape the communication flow.</p> <h2 id="believing-that-notification-is-enough" style="position:relative;"><a href="#believing-that-notification-is-enough" aria-label="believing that notification is enough permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Believing that notification is enough</h2> <p>It’s a way to push responsibility to the subscriber and potentially reduce the work on our side.</p> <blockquote> <p><em>“You got the notification; just do what you want with it.”</em></p> </blockquote> <p>That’s not the greatest move, as if the subscriber wants to get that package was sent, and we’re just publishing the <em>ShipmentStatusChanged</em> event with the shipment id. Each time they get the notification, they need to react and query our API and decide if the information is valuable to them. That also leads to race conditions, which I will cover in more detail later in this article.</p> <p>There’s little benefit between regular polling from the API with the <a href="https://en.wikipedia.org/wiki/Cron">cron job</a>. Maybe a bit better resource utilisation, but that’s also disputable.</p> <h2 id="going-to-the-extreme-with-events-granularity" style="position:relative;"><a href="#going-to-the-extreme-with-events-granularity" aria-label="going to the extreme with events granularity permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Going to the extreme with events granularity</h2> <p>You can read in multiple places that events should be granular; the smaller, the better. That advice can be misleading. My take is that <strong><a href="/en/events_should_be_as_small_as_possible/">Events should be as small as possible, but not smaller</a></strong>.</p> <p><strong>If we trim an event from data, we make it just a clickbait headline.</strong> That’s not usable. We should give more context and let subscribers decide if it’s interesting for them or not.</p> <h2 id="not-thinking-about-events-as-the-api" style="position:relative;"><a href="#not-thinking-about-events-as-the-api" aria-label="not thinking about events as the api permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Not thinking about events as the API</h2> <p><a href="/en/internal_external_events/">I explained in another article that we should treat events as an API</a>. Embracing that events are contracts between a publisher and subscribers is a must. That helps with shaping proper communication and embracing the needs of both sides. Even if the event-driven communication is publisher-driven, ignoring subscribers’ needs is a no-go.</p> <p><strong>OK, so we know the reasons now, but what issues clickbait events can cause?</strong></p> <h2 id="chattiness-of-the-communication" style="position:relative;"><a href="#chattiness-of-the-communication" aria-label="chattiness of the communication permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Chattiness of the communication</h2> <p>I’m sure you know the person who asks you a hundred times:</p> <blockquote> <p><em>“Hey, did you finish already?”</em>.</p> </blockquote> <p>We usually answer:</p> <blockquote> <p>“I told you that I will ping you when I’m done, right?”</p> </blockquote> <p>And we get:</p> <blockquote> <p>“Yeah, yeah, I know, just double-checking.”</p> </blockquote> <p>That’s precisely what’s happening with clickbait event-based communication. In theory, we’re making our communication more push-based and asynchronous, but as we’re not giving in the answer any more context status, we’re getting constant queries. Maybe less than with continuous polling, but still a way too much.</p> <h2 id="exponential-growth-of-calls" style="position:relative;"><a href="#exponential-growth-of-calls" aria-label="exponential growth of calls permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Exponential growth of calls</h2> <p>If our systems are distributed, and we have write models focused on the business logic and read models on the query needs, then it may appear that we don’t have a single API to query to get all data. Write model endpoints will usually contain a minimum set of data, e.g. only dictionary keys instead without description, etc.</p> <p>Read models may be fine-tuned for the module’s needs, and our subscriber’s needs may differ.</p> <p>All of that can end up with exponential growth of calls. First, we query for raw data, then additional calls to get dictionaries, then other nested data and too often additional data from other modules.</p> <p><a href="https://www.youtube.com/watch?v=38AYeNGjqg0&#x26;t=37s">Release the Kraken!</a>.</p> <h2 id="our-communication-is-direct-and-error-prone" style="position:relative;"><a href="#our-communication-is-direct-and-error-prone" aria-label="our communication is direct and error prone permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Our communication is direct and error-prone</h2> <p>Just like the insurer’s hotline may not be available, our services also may not. When we get an event and we try to query data from an external service, it may be down. If we add to that <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">at-least once delivery, idempotency, etc.</a>, we can even make a <a href="https://www.cloudflare.com/learning/ddos/what-is-a-ddos-attack/">DDoS attack</a> on our services.</p> <p>Of course, we can have <a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">retry policies</a>, <a href="https://en.wikipedia.org/wiki/Circuit_breaker">circuit breakers</a>, etc., but they’re not solving that issue; they’re just decreasing the scale of it.</p> <p>One of the biggest benefits of event-driven communication is that it enables our services to be loosely coupled and resilient. By passing events, we can reduce direct connections to a bare minimum. Of course, as long as we put enough context in them.</p> <h2 id="race-conditions-between-notifications-and-queries" style="position:relative;"><a href="#race-conditions-between-notifications-and-queries" aria-label="race conditions between notifications and queries permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Race conditions between notifications and queries</h2> <p>The way we envision our communication looks like this:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 703px; height: auto" > <a class="gatsby-resp-image-link" href="/static/79e0b819640084553d2b955fa6879524/0440a/2023-11-05-01.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABhElEQVQoz22S22oUQRCG53V9AfFCb30BEV9AUSSoFwreBEJc1Hi4iKJmNyHZTXZnszO7HsDs9KGqPume2XFBC4ouvqb/6vqpolqumB59pJmPSKGqbRrE1QT5fo5lbqgKCoT6jLjacO0zxkiRROoXtynvXwMMU4epZvHq6U3Kx9dzbdpgKrle7NygenaL9geeTSTRLCi/5oR6wqPDe+yPn7QCif+c0SzOePjpLq/Pn//lP6b45QUPDu/wfvqy5WatYCqs6/BussfncoBqg4vrnr8d7/Ll8gDTdc9TvhnvMqo/oOrw0hAlUnjviSHgvSMbpEYMDSEGnP+NxLiZB4kOHx0uXCFROi7E6AjR48KaYrFYUFUV6ayXNVjo+kOQJpdqiorrRhZi51vmvYeWGxX8Ew3Hg1OGr8bJLUwNy/qO0f4xw8E4i6ZGLV8z3Dvh5OACJWwLbh4a89El81Hdm22dmeW3ktlR1fNuZmZfS6rTVX5bbO9R2jMR7VuISH+3zVX0vzzt4R8uQrQXWVQiZwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="diagram 1" title="diagram 1" src="/static/79e0b819640084553d2b955fa6879524/0440a/2023-11-05-01.png" srcset="/static/79e0b819640084553d2b955fa6879524/36ca5/2023-11-05-01.png 200w, /static/79e0b819640084553d2b955fa6879524/a3397/2023-11-05-01.png 400w, /static/79e0b819640084553d2b955fa6879524/0440a/2023-11-05-01.png 703w" sizes="(max-width: 703px) 100vw, 703px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Everything is in the right order. The shipment module first updates the read model and then sends the <em>Shipment Status Changed</em> event. The order management module gets the notification and queries the read model. Then, the following events repeat the same pattern sequentially. If we’re lucky enough, this can also happen in reality, but…</p> <p>We may also get notifications with a delay:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 703px; height: auto" > <a class="gatsby-resp-image-link" href="/static/119537a163c102eba42d6e9d974c2b69/0440a/2023-11-05-02.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABhklEQVQoz4WSy2/TQBDG/R9y48w/hJC49MCtB448DtzaAxXiJRW1QS2lDhBio9ghTpo69np3Z/aH1k4KEq9ZfZr5tDuf5rHJYrFgPp8zS09Qs0YDiKmRVYaKICL4VY6aGlV6HqEaob8hcd4TvGXy4A7rtw+J1qVHTO7fArMEHNO929QnT/s7gvAvS0II8RV+9pEiO2V/dI9s9hr9esaL8TOenO8h2Yh8+o7907tk6zNEN0gQQlA06I1Y1NoKDmbUcpg+ojLfe35RHvNyctDHtV1xcPmYqi0wfkXdFTi1OOlwYgbhAEnbtjRNQ9tsaJsGfOwq4JwF8dDHDnEGdOhYJdB2V2xMifeC85bW1ljXkVRV1S9luVxSliX15vqmfI1QS1BDCHEJhhD5tiunNVav2PXo6Uj+NNhdQvRh53thB6HFmzXp8zHzScHF0QeWxYx8NOPzcUYSV90n/IK/2naBYhvSV5+o8pLxmy9cL1Z8O8+Zvi9Ihj+l/8HPfyc9j5Vvq2cYze78AClutGs/FjYwAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="diagram 2" title="diagram 2" src="/static/119537a163c102eba42d6e9d974c2b69/0440a/2023-11-05-02.png" srcset="/static/119537a163c102eba42d6e9d974c2b69/36ca5/2023-11-05-02.png 200w, /static/119537a163c102eba42d6e9d974c2b69/a3397/2023-11-05-02.png 400w, /static/119537a163c102eba42d6e9d974c2b69/0440a/2023-11-05-02.png 703w" sizes="(max-width: 703px) 100vw, 703px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Some may say that’s not an issue, as you’ll still get the latest state. And that can be fine for some scenarios (e.g. <a href="https://en.wikipedia.org/wiki/Webhook">webhooks</a>). If we’re just updating the read model and are interested in the latest state, that can also be fine for some time, but…</p> <p>We may also end up in such a case:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 703px; height: auto" > <a class="gatsby-resp-image-link" href="/static/07f4e4948ad997dfc8d885b9e0e53178/0440a/2023-11-05-03.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABg0lEQVQoz4WST28TMRDF9xvyBRA37nwNjghO4QwHDhwQUsU1KqoAoUipCg2hS5uk4Gw2C83uer22xz9k040A8WekJ8/Y1ps3z87KsmRTFKj8BDE1PoA3LbJTiPf4iCuFxL14NuyJRxLkF2TWOrw1fBrdYvfqETHMfMzF/RvQVYCwGN2kmT5PZwThX5GFEFLSL49RiwmPTx6w/HyEO5twlB/w7P1DZDUln73mzt3bzJeT2PKHoiCEnxpErj1hjE56XsyeUHZFqt+pN4zzg5RX9YbR03usynOMVOzMF5z0WDEJkZwAmdaatm3RbYNuW3AQfMDaHrwlGmetRVx3LSP6GNDmK01X4JxPd3Vf01tDtt1uKYqCqqpYr9fUTb2XHyFiCNKl0bzTiPQMM1mpsVzta4ch+5Oxgw2JNK1CiETSQ9C4bsfZy5zNRcHs8JRqrbg8VpxPLsmiuYOaAX+N9AABbxo+HM4pF4rT8Ue+qZLldEH+dkUW/9Tvf+n/iMqv1ROQMNTCd+EPswxDF7oaAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="diagram 3" title="diagram 3" src="/static/07f4e4948ad997dfc8d885b9e0e53178/0440a/2023-11-05-03.png" srcset="/static/07f4e4948ad997dfc8d885b9e0e53178/36ca5/2023-11-05-03.png 200w, /static/07f4e4948ad997dfc8d885b9e0e53178/a3397/2023-11-05-03.png 400w, /static/07f4e4948ad997dfc8d885b9e0e53178/0440a/2023-11-05-03.png 703w" sizes="(max-width: 703px) 100vw, 703px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>The final event may have deleted the resource or made it unavailable (e.g. completed or cancelled shipment). Then, we won’t be able to get any meaningful information.</p> <p>Also, if we want to trigger a particular process based on the partial step of the process, we’re losing such capability.</p> <p>Sometimes, events can also be delivered faster than the read model updates:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 703px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b6886a9c7ab26c18a82cd0a62b8086b2/0440a/2023-11-05-04.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 43%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAYAAAAywQxIAAAACXBIWXMAAAsTAAALEwEAmpwYAAABl0lEQVQoz22R3W4TMRCF83S8ELwBl9wg8QpI/FyA1HKBqCCIH1EVKOJHVRsStU2ySba7TUKy6+16PP6Q7aYVqJZGe3zG5xyvp3NWlGT9nzTnY9QJtjhG7QWqHlXFqWIXY9y6RD2Jcy7W/9haSwcg37rL7OltZDkhu38LM/qGB0RsaDN/cof5q3to4GziblrBOBq25RBb9KhXJcP9LmIWnBU5bWPZ+f2M17sPaPJjWlvjBbZ/PeTtYDuavOw95t3Ji4jFCR3vNd4mrNks58fRUcT7Xz8zyoZ0B8/5OH4TucqUOHF0+1t8Ot0hCK8wYMXSEXGIvcBZg6kbqnqFczVNXbE2q5TkwUmLSkNjDZsbOHXXfecQETrT6ZQ8nzCbjlgsl0B43CqeUyziWtSHAQnep/drtUbV4SOfBhJwNORSijecfpmQ9XJgzclexvhwEuO9v7Q3NdODc0xV4aPmpqF4ksBbeh/6ZIdFaNF7P2B8kKc4TUozL9l99J1l8Sf9aUq6+kbDsEm1Cdzg68ObCr5iBa/+H35TwfAv+mOxu5WiAIEAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="diagram 4" title="diagram 4" src="/static/b6886a9c7ab26c18a82cd0a62b8086b2/0440a/2023-11-05-04.png" srcset="/static/b6886a9c7ab26c18a82cd0a62b8086b2/36ca5/2023-11-05-04.png 200w, /static/b6886a9c7ab26c18a82cd0a62b8086b2/a3397/2023-11-05-04.png 400w, /static/b6886a9c7ab26c18a82cd0a62b8086b2/0440a/2023-11-05-04.png 703w" sizes="(max-width: 703px) 100vw, 703px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In that case, we won’t even know if the record is not yet available or was made unavailable.</p> <p><strong>We’re getting eventual inconsistency. Curtain</strong></p> <h2 id="how-to-deal-with-clickbait-events" style="position:relative;"><a href="#how-to-deal-with-clickbait-events" aria-label="how to deal with clickbait events permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How to deal with clickbait events?</h2> <p>The answer is simple: <a href="/en/internal_external_events/">treat your events as an API</a>. Enrich your events with needed data. Consider subscribers’ needs.</p> <p>For your inner module needs, you can still keep them granular but not clickbaitish. For external module needs, ensure that they get the information completeness guarantee. Thanks to that, they won’t need to know too much about your modules and querying, creating a fragile, unmaintainable mess.</p> <p>Beware of replacing our clickbait <em>ShipmentStatusChanged</em> event with the following:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">record</span> <span class="token class-name">ShipmentDelayed</span> <span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShipmentId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">ShipmentProviderSelected</span> <span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShipmentId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">ShipmentProviderSent</span> <span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShipmentId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">ShipmentCompleted</span> <span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShipmentId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>This is a variation of <a href="/en/property-sourcing/">Property Sourcing Anti-pattern</a>, that we could call <em>Status Sourcing</em>.</strong> Those events are a bit better, but still clickbaits.</p> <p>Moreover, if we don’t map them to some unified meaningful external event, then each time we add a new status, other modules will need to update their code to get information about it. That creates more coupling.</p> <p><strong>Events should contain all information about the fact that was registered; if we’re passing just a subset, then we’re getting ourselves into trouble.</strong></p> <h2 id="when-such-an-event-can-be-a-correct-usage" style="position:relative;"><a href="#when-such-an-event-can-be-a-correct-usage" aria-label="when such an event can be a correct usage permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>When such an event can be a correct usage?</h2> <p>From pattern to anti-pattern, there’s a thin distance: the context. In which context then clickbait event can be a decent tradeoff?</p> <p><strong>I already mentioned one: webhooks and websockets.</strong> Pragmatically, if we just want to expose the notifications about the progress of our process, and we have zero idea of how subscribers will use it. It may be an acceptable tradeoff not to expose additional data and reduce the set of sent data. It may be an okayish first step to an event-driven approach, but it shouldn’t be the last one.</p> <p><strong>Security and privacy can be additional aspects.</strong> If our notifications protocol is not secure enough, or we want to have more control over data (e.g. <a href="https://en.wikipedia.org/wiki/Personal_data">personally identifiable information</a>), then we shouldn’t broadcast it.</p> <p><a href="https://verraes.net/2019/05/eventsourcing-patterns-forgettable-payloads/">Mathias Verraes described the Forgettable Payload pattern</a>, which can be a correct modification for our clickbait event.</p> <p>We could add, to the event data, a URL to the endpoint that will return the full shipment data, e.g.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">record</span> <span class="token class-name">ShipmentStatusChanged</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShipmentId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Url <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// and create it as</span> <span class="token class-name"><span class="token keyword">var</span></span> shipmentStatusChanged <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShipmentStatusChanged</span><span class="token punctuation">(</span> shipmentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"shipments.com/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">shipmentId</span><span class="token punctuation">}</span></span><span class="token string">/versions/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">shipmentVersion</span><span class="token punctuation">}</span></span><span class="token string">"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>As you see, the URL contains a version of the resource. Thanks to that, we’ll be less prone to race conditions. To deal with eventual consistency, we should either implement a retry policy on the subscriber side or publish an event after the read model is updated.</p> <p>This gives us more control, as we can centralise the privacy-related data and use more granular authorisation rules.</p> <p>Of course, we need to remember that’s not the ultimate solution, as once someone queried the data, they can do whatever they want, for instance, cache it or store it locally.</p> <p>Read more in <a href="/en/gdpr_in_event_driven_architecture">How to deal with privacy and GDPR in Event-Sourced systems</a>.</p> <p>Or watch:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/7NGlYgobTyY?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>We can also use the same for blob data.</strong> For instance, if we know that subscribers are interested in the PDF containing the current shipment letter, we can either send a URL to versioned blob storage (e.g. <a href="https://aws.amazon.com/s3/">AWS S3</a>) or our service for downloading files.</p> <p><strong>Read also other article in Anti-patterns in event modelling series:</strong></p> <ul> <li><a href="/en/property-sourcing/">Property Sourcing</a>,</li> <li><a href="/en/state-obsession/">State Obsession</a>,</li> <li><a href="/en/i_will_just_add_one_more_field/">I’ll just add one more field</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Women in IT]]>https://event-driven.io/en/women_in_it/https://event-driven.io/en/women_in_it/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 500px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8c0b85d22a787b1c37584605e761fb77/b30f8/2023-10-27-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 63%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABpUlEQVQ4y52TyYoCQRBE+7f9BkU8+AMiXlQ8qLh14664I+77gorgT8TwSrrR0blMQlJVaXRkRFZpVSoV2bYt27Gf62u+1Rw5Jp9n5wVXKpXUbDbl8/lkXa9XHY9HnU6nf+fhcNDj8VAwGJQF2Xq91maz+TN3u91bUttut97vq9VKCPP7/U9CF/CafMhK99ls5uVyuTQ1F0Mi6Ha7KRAIyDqfz9rv9wZEsnfJ2Pf7fY1GI6/pYDDQeDz2SMk3Qg6AhsOhB14sFp4y9r8DHEpp+EHIDbVaLfV6PaMkm82qWq3qcrmYm4OUPQ1YCRxQ/0rY6XR0v9/NbTFYyKihANU0mEwmqtfrhpTAxXw+/04ImAKkzAswb8u1lEwmlUqljCJXHW7+nGG73TaKCKwzH6zSiOdQLBY1nU4NJpFIKBqNmrOr7uOW6YwyAnKeEfYg5yOIGIfjOEqn08rn82o0Gobgq0IsYo+IRCIKh8MKhULGJtYLhYJyuZwhrtVqisfjZsUJRB8KAWYyGfGf5gIAu3PFmvt0eK+oi8Vipkm5XFa32zWOGI1L+ANxRnquGzVE1AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/8c0b85d22a787b1c37584605e761fb77/b30f8/2023-10-27-cover.png" srcset="/static/8c0b85d22a787b1c37584605e761fb77/36ca5/2023-10-27-cover.png 200w, /static/8c0b85d22a787b1c37584605e761fb77/a3397/2023-10-27-cover.png 400w, /static/8c0b85d22a787b1c37584605e761fb77/b30f8/2023-10-27-cover.png 500w" sizes="(max-width: 500px) 100vw, 500px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I used to play football. I wasn’t talented, but still, I loved it. Football is a team sport, and playing is not the only essential part of that. The other is the locker room.</strong></p> <p>The locker room has its secrets that cannot be revealed outside it. The locker room is homogeneous; you have to keep a standard line. The locker room is also filled with mockery. Hiding clothes, laughing at farts, that sort of thing. It’s hilarious; you can imagine.</p> <p><strong>Embarrassment of one is fun for others. Still, counterintuitively, all of that brings the team together.</strong> Of course, if you’re having fun. If you’re not a member of the pack. What happens next? You’re sidelined and don’t play. What’s the worst for a football player? Not playing. You don’t play; you get discouraged. You get discouraged, and you leave. Adapt or leave.</p> <p>Mockery is a primitive initiation and onboarding process.</p> <p>But meh, luckily, in IT, we’re not primitive; we have higher IQs, broader horizons and are above those masculine rituals, right? Right?</p> <p><strong>Did you have friends you liked but couldn’t stand the rest of their company?</strong></p> <p>Or, on the contrary, have you seen such a well-coordinated group that you wanted to be part of, but you didn’t feel cool enough? You tried to be chilled, made a joke and were only met with eye rolls and ignoring?</p> <p>Some time ago, I was running the recruitment process. I realised that I didn’t get a single woman in the twenty or so job interviews I conducted. What’s more, I didn’t get any female CV either.</p> <p>I was invited once to a big conference where the organisers gathered one woman out of about ninety speakers. <a href="https://blog.pragmaticengineer.com/devternity-fake-speakers/">There were conferences that created fake women profiles to cheat that they care about diversity</a>.</p> <p>Do you already know what I’m getting at? What am I wandering around?</p> <p><strong>I was raised with a conservative mindset.</strong> Respect for every person, but still very traditional. I was a prominent opponent of parities. I argued that putting quotas is unfair. I argued that we live in the 20th and 21st centuries, where everyone has equal rights. I asked why women do not choose women’s politicians since there are more of them than men. Why are they not in solidarity with each other? I repeated chauvinistic jokes, trying to show that I had some distance from them. I could have joked like that since everyone knew I didn’t think so.</p> <p>Did lightning strike me that made me reevaluate my life and spread the good news? None of these things.</p> <p><strong>I am a programmer. I have an analytical mind.</strong> Sometimes, I overthink, especially myself. I analyze, I think, I conclude. That’s nothing special, nothing complicated. Sometimes, all it takes is a little reflection. Day by day. Positively. Little by little does the trick.</p> <p>Eventually, I realised that it’s effortless to make wise statements when you’re a middle-aged man with a good job in a top profession.</p> <p><strong>It’s not like if someone didn’t manage to get to the same place, then it’s <em>just their choice</em>.</strong> It doesn’t mean that they <em>could work harder</em> or <em>made another choice</em> or that <em>they didn’t want to be here</em>. Sometimes, there is simply not enough enthusiasm to fit into a hermetic group. Sometimes, a person just doesn’t feel good enough. Only some people like to join a party without an invitation. Some won’t come without it. And it’s not because they don’t want to; they just don’t feel welcomed.</p> <p><strong><em>“With great power comes great responsibility”.</em> Being a privileged person means we have a great responsibility towards less privileged people.</strong> There’s nothing wrong, by itself, with being privileged or having a better position. Nothing to be ashamed of. It’s just a fact where we are. There will always be someone with more power, more voice, etc. We should just embrace that. The issue, in my opinion, is when privilege is changing into gatekeeping. We should be the ones opening the doors and letting people in. Without establishing certain common-sense parities, we will not break this wall. We should not close our eyes, pretending we don’t see the problem. As men, we should realize that this is not normal. It just isn’t. Dot.</p> <p><strong>Are we the only ones to blame for this? Of course not. Women should also not allow themselves to be tunnelled by us.</strong> Real-world example from my project. We had an unplanned visit of the client to our office. The office manager from headquarters asked my colleague from the QA team to organise a meal for the client and other people. The colleague agreed to do so. What’s wrong with that? Both the office manager and the colleague from my team were women. And it’s not a rare scenario.</p> <p><strong>My women friends, you shouldn’t put yourself in this situation. Any other employee, including men, would have the same obligation as you to deal with such matters.</strong> Likewise, don’t let yourself be forced into stereotypical roles of testers, analysts or middle-level managers. Of course, none of these professions is worse than being a developer, but I have often encountered a situation where a woman, after computer science studies, even though she wanted to be a programmer, started with such roles <em>“because it will be easier to start this way”</em>. You truly are as capable as men, and you’ll do well enough or even better than us.</p> <p>Some say that women just don’t want to be programmers. That’s just untrue. Our industry was one of the most feminine engineering industries for a long time. It changed in the 90s when men realised they could earn much money by being programmers. Read more in <a href="https://www.nytimes.com/2019/02/13/magazine/women-coding-computer-programming.html">The Secret History of Women in Coding</a> and <a href="https://coding-is-like-cooking.info/2017/04/proportion-of-women-programmers/">Proportion of Women Programmers</a>. It’s also not surprising that more women are working as frontend developers than back-end, as many developers see it as less prestigious. Read more in <a href="https://melissamcewen.medium.com/is-frontend-development-sexist-220040c952b1">Is Frontend Web Development Sexist?</a>.</p> <p><strong>If you don’t think that’s as bad as I picture, then rethink if you just didn’t look around.</strong></p> <p>I heard from men that parity like <a href="https://www.iod.com/resources/blog/inclusion-and-diversity/the-european-women-on-boards-directive-what-it-means-and-why-it-matters/">The European Women on Boards Directive</a> are bad because <em>“you need to have a one women in the board before you hire competent people.”</em> Which is, of course, a pure chauvinism that can be inverted to <em>“you just need to find a single competent woman and then you can hire the rest of incompetent men”</em>. Of course, enforced parity in the perfect word should not exist. But our world is unbalanced, and we need to break the unwanted status quo.</p> <p>When I often talked about the woman from my team, other devs or managers instantly assumed they were QA team members.</p> <p>In one company I’ve worked, I asked at the managers’ meeting why we had only two women out of 60 people in our group and what we could do about it. I got the answer from the top manager: <em>“Okay, fair, let’s get some good-looking apprentices and raise them</em> followed with a cackle.</p> <p>I once had to go to the CEO after learning about several cases of harassment made by managers to their female employees. And I heard and read similar stories like that.</p> <p><strong>This shit is real.</strong></p> <p><strong>So now, my fellow men, am I urging you to make a revolution or turn your worldview upside down?</strong> I wouldn’t dare. I’m not one of those who tell others how to live. I encourage you to reflect and consider what you can do with this topic. What would I, such a smartass, suggest?</p> <ol> <li>Pay attention to how many women are in your company and if they’re paid, respected equally to their competence.</li> <li>Look around and see if your environment is open and friendly. Consider whether you would like your wife, girlfriend, sister or daughter to work in such an environment.</li> <li>Listen more carefully to what you say and what others say, whether the jokes are appropriate and really funny.</li> <li>No, conferences and meetups are not places where women are coming to be picked up.</li> <li>Ask HR about the candidates and their CVs. Ensure that they’re inclusive.</li> <li>Consider whether you treat every CV equally, regardless of gender. Are you looking for a woman for QA and a man for DEV roles? I know that’s not easy; there are some techniques like <a href="https://hbr.org/2023/06/when-blind-hiring-advances-dei-and-when-it-doesnt">Blind Hiring, but they not always bring expected results if we’re not doing it sanely</a>.</li> <li>Pay attention to others when they misbehave.</li> <li>Be proactive; don’t expect it to happen magically by itself. Don’t let yourself fall into the <em>this-is-a-wider-issue</em> or <em>it-will-take-years</em> mentality</li> </ol> <p><strong>I always tried to make my teams diverse. Not only because of the higher goals but also because of pragmatism.</strong> Diversity just works better. The best-performing were those that were most diverse. Different perspectives improve our design; we can predict more scenarios and complement our skills. Women bring a lot of elements to the team that men often ignore: pragmatism, striving for consensus, and rationality.</p> <p><strong>As I wrote, I started on the ground level. But I’ve made my way; it’s doable. You just need to open your mind, observe, try to make small steps and improve.</strong></p> <p>Read also more in:</p> <ul> <li><a href="https://www.nytimes.com/2019/02/13/magazine/women-coding-computer-programming.html">The Secret History of Women in Coding</a></li> <li><a href="https://coding-is-like-cooking.info/2017/04/proportion-of-women-programmers/">Emily Bache - Proportion of Women Programmers</a></li> <li><a href="https://medium.com/tech-diversity-files/if-you-think-women-in-tech-is-just-a-pipeline-problem-you-haven-t-been-paying-attention-cb7a2073b996">Rachel Thomas - If you think women in tech is just a pipeline problem, you haven’t been paying attention</a></li> <li><a href="https://www.theguardian.com/technology/2017/mar/14/tech-women-code-workshops-developer-jobs">Guardian - We can teach women to code, but that just creates another problem</a></li> <li><a href="https://hbr.org/2014/12/rethink-what-you-know-about-high-achieving-women">Harvard Business Review - Rethink What You “Know” About High-Achieving Women</a></li> <li><a href="https://theconversation.com/more-women-are-studying-stem-but-there-are-still-stubborn-workplace-barriers-190839">Lisa Harvey-Smith - More women are studying STEM, but there are still stubborn workplace barriers</a></li> <li><a href="https://www.rappler.com/newsbreak/in-depth/massive-hidden-cost-keeping-women-offline-digital-gender-divide/">Lian Buan - Insidious, expensive: The massive, hidden cost of keeping women offline</a></li> <li><a href="https://peerj.com/articles/cs-111/">J. Terrell, A. Kofink, J. Middleton, C. Rainear, E. Murphy-Hill​, C. Parnin, J. Stallings - Gender differences and bias in open source: pull request acceptance of women versus men</a></li> <li><a href="https://biancatrink.github.io/files/papers/TOSEM2021.pdf">B. Trinkenreich, I. Wiese, A. Sarma, M. Gerosa, I. Steinmacher - Women’s Participation in Open Source Software: A Survey of the Literature</a></li> <li><a href="https://thenewstack.io/why-women-are-underrepresented-in-open-source/">Barbaño González - Why Women Are Underrepresented in Open Source</a></li> <li><a href="https://emnaayadi.wordpress.com/2024/04/09/diversity-in-tech-conferences/">Emna Ayadi - Diversity in Tech Conferences</a></li> <li><a href="https://soundcloud.com/stack-exchange/podcast-123-jon-skeet-wants-you-to-be-a-feminist">StackOverflow Podcast - Jon Skeet Wants You to Be a Feminist</a></li> <li><a href="https://mattwynne.net/feminism">Matt Wynne - Feminism</a></li> <li><a href="https://totalent.eu/20-ways-to-hire-more-women-into-engineering-and-combat-the-gender-gap/">Jasper Spanjaart - 20 ways to hire more women into engineering and combat the gender gap</a></li> <li><a href="https://www.youtube.com/watch?v=JQL4doMy73w">Heather Wilde - How to Close the Diversity Gap</a></li> <li><a href="https://blog.pragmaticengineer.com/devternity-fake-speakers/">Gergely Orosz - A Tech Conference Listed Fake Speakers for Years: I Accidentally Noticed</a></li> <li><a href="https://github.com/fempire/women-tech-speakers-organizers">Fempire - A list of women tech speakers &#x26; organizers</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[In the defence of Object-Relational Mappers]]>https://event-driven.io/en/in_the_defence_of_orms/https://event-driven.io/en/in_the_defence_of_orms/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e2ae2c80857380bf9d06ad103483ebef/a331c/2023-10-22-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4gAAAuIAHVHB4bAAADYUlEQVQ4yz3Oy1NaBxzF8R+IosYREy2iRkVjosGgooIMik+4iF5Q4IqXR0TFB8a3U62RNLVtnKldmGyaLtJOptPJ9A/oH9HuO5PpotOs+wek229HF12c1Tlz5iNpLUFCDZNQp9GiCplEmNxilHQizNneNi9OjtHVKTJzAZLzEyTnJ1nWZ9nKaWyt6iwl58npMVYzC6TnAsjmUobr01Q8hh5XySVV8ukYS8kIaS2E4nlEo6mEzrrbaJExEpFxRof7WdKjN9vUfJit5RQ7+QybqQhytldgZ6NAPpshuxgnt6iykoqS02d5rM9Q1KbZ7XcwarWSVqeYG+lnsNqM0mXHbbXQWGFmQQ1SPCzw6doicnl2yNOjp+xuFFjJJMlqYWLKCJFJD1PBIYoLIZ65HBT6ekjFFY4UH8cOO8WxPry1FspE6LDWs60n2c5pyPnuOs9OPyevL5Dwe7nYXOafD3/z/s8/ON9f5pt2O+sNTez2ufhx5zG/FDdZsVr4Ka5y5fPgNgqq38eLgye8ubpE1OkQexvruKwW7puE7L1G/vr9Nz7++5GXvj5yIihlZhbbOimI8Dqf5cvUPMeDPXw/0sNnXg9PtByvv73gh9MDRBl2o82E6f2kGkdVJasdTXwdjfLu4Dl5EZYNwroI2q0qUgYjr/ZPeHV+SUN1FUmng2J2h3RggeO1fV4WVhC33UZE8dFWd4d4SyOnPe1crsX48P5XTirK0ET4zmblbTLO8+FhzpMZ1hSVUlMpAw+cHM8lSfimmB2YYiWgIm01lWSdd8nV24hYqlHqKjnaP6R48ZZYWTkPr3W1d/gqMMa71Qw/r6Z5k9X5IhzkaibAvsvBaEc3oR4/Y90+ZKi8DF0MzIkwLAbuGg003WrAZnlIgwi9BgODIgyJELZUszM2wlEoQM7t4mRijG23i6DTTXRgklDvKOLp6qOt4R5eMTAiQpMI9ppaujudNIrgESEowoAIThH66+vpam5FrrtuN+tqCv/9R8wNjjPR5UF6PRFMNifm0qobRbcIt00mmlrtVIhgE+GBCK0idJlKaDEIJhGaq6pYndVJT8XwtjvQ/DMEur2Iw+GnomUIU00rPcYSHCJUGI2Um803iv9jMNBsLqPdaMQsgqutg83kGvHJKHpwnpyiEXaN8x+czskoZoFuzwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e2ae2c80857380bf9d06ad103483ebef/a331c/2023-10-22-cover.png" srcset="/static/e2ae2c80857380bf9d06ad103483ebef/36ca5/2023-10-22-cover.png 200w, /static/e2ae2c80857380bf9d06ad103483ebef/a3397/2023-10-22-cover.png 400w, /static/e2ae2c80857380bf9d06ad103483ebef/a331c/2023-10-22-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I’m happy I didn’t have to use <a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping">Object-Relational Mapping tools</a> in the last few years.</strong> They’re solutions to some set of problems, but those are the problems I try to avoid having in general. Still, I remember times when we didn’t have ORMs, and I can say that they write better SQL than the average developer would write manually.</p> <p><strong>Yet, I’m fed up with the takes “ORMs are bad”.</strong></p> <p>They’re easy targets to hit, but they’re kinda whistleblowers of other design and organisational issues in the company. Usually, when you struggle to use them, that means that you should get back to the drawing board.</p> <p>They’re such as we made them, and there’s a reason for their existence: reducing cognitive load. Instead of picking on them, we should analyse why they’re popular and try to make other tools and practices easier (like in Marten).</p> <p><strong>Of course, they’re making some things too easy.</strong> For instance, doing multiple nested joins or modifying a whole tree of dependencies. All that looks easy, as doing things in memory, but it’s not, as it ends with a bloated set of operations and quite often, <a href="https://learn.microsoft.com/en-us/ef/core/performance/efficient-querying#beware-of-lazy-loading">the N+1 problem</a>.</p> <p><strong>Sometimes, that’s just cheating from their creators.</strong> For instance, Entity Framework Core till version 3.0, if it couldn’t find the proper query in SQL, it was getting results into memory and filtering them. See <a href="https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client">here</a>.</p> <p>But then, if people take things for granted that <a href="/en/the_magic_is_that_there_is_no_magic/">all will magically work</a> and abuse ORMs, is it an issue with the tool or a people’s problem? That’s what I mean by organisational issues: lack of proper review, design phases and collaboration or just pure laziness. Some folks will always try to find the easiest way to <em>“just add one more if”</em> and call it a day. To change their attitude, we need to build a better process. Tools may help in that, but they won’t solve that issue.</p> <p><strong>I heard once that it’s always good to understand at least an abstraction level below we’re working.</strong> So, if we’re using ORM, it’s still important to understand Relational Databases and SQL because we won’t be able to use them efficiently. ORM will then help us to reduce tedious work, but we can understand when it’s better to fall back to regular SQL or RDB features.</p> <p>Of course, some issues come from the fact that people are trying to use the Relational model where it doesn’t suit their use case. That’s why I prefer a document model instead of a tabular one as the default choice. Most of our applications are more suitable for it, as we’re still moving the regular physical world (so documents) into computers. (Read also more in <a href="/en/strategy_on_migrating_relational_data_to_document_based/">General strategy for migrating relational data to document-based</a>). Luckily, nowadays, with Docker and Cloud Native managed services, it’s much easier, and we have less friction to use other stuff than tabular data.</p> <p>The relational model is much better for advanced filtering, etc. So, reading and correlating data via their relationships. It’s also pretty good for optimising the data space and doing on-point updates. But it’s not great for changing a wider object schema with nested data. And that’s the schema we usually have in our business logic.</p> <p><strong>Nowadays, ORMs are pretty smart and can do advanced mappings, but they still have limitations.</strong> And we should embrace those limitations instead of unifying all models into one. If we do that, we’re getting an inflexible, bloated model that’s hard to maintain and understand. Our relational data and ORM tooling limitations are dragging us. Read more on how to <a href="/en/slim_your_entities_with_event_sourcing/">slim your aggregates</a>.</p> <p><strong>The other extreme is trying to break our models and doing mapping back and forth.</strong> That’s, too often, ending with complex, fancy data structure comparisons between domain model and entity to find what’s changed and do optimised updates. That’s never performant and always prone to mistakes. Of course, we have options to do it better; I showed that in <a href="/en/how_events_can_help_on_making_state_based_approach_efficient/">How events can help in making the state-based approach efficient</a>.</p> <p>I remember days without ORMs; they were full of gigantic_<a href="https://www.dodgycoder.net/2011/11/yoda-conditions-pokemon-exception.html">stringly-typed code</a>_ vulnerable to SQL injection. I know where suggestions of <em>“just us SQL”</em> can lead us. How viable it is depends on the context we’re in. Removing ORMs out of the game won’t automatically fix our issues.</p> <p><strong>In my opinion, the main issue why people struggle with ORMs is that they’re trying to squeeze a pig in a box.</strong> They believe they can do all at once without embracing the reason they were built for. As their name suggests, they were made to facilitate mapping from relational storage, which was (is?) the de facto storage standard for many years. The standard that wasn’t suitable for Object-Oriented programming. The fact that we do the mapping doesn’t mean that this model should represent something more than a storage layer.</p> <p>See the difference between:</p> <ul> <li><a href="https://github.com/oskardudycz/slim-down-your-aggregate/blob/6bf937f92708b2bd1fef117f0b78de6cb654965e/csharp/Original/PublishingHouse.Persistence/Books/Mappers/BookEntityMapper.cs#L67">typical wild ORM usage</a>,</li> <li><a href="https://github.com/oskardudycz/slim-down-your-aggregate/blob/9d7dbdea044991e446ce69ba438e2589176bda49/csharp/Slimmed/PublishingHouse.Persistence/Books/Repositories/BooksRepository.cs#L38">more focused on behaviour and tools capabilities</a>.</li> </ul> <p><strong>Trying to use ORMs for everything isn’t the issue with the tools; that’s a design and organisational issue.</strong> Blaming and hating tools won’t help. It’ll just make an easy excuse for us, as tools won’t defend themselves.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Internal and external events, or how to design event-driven API]]>https://event-driven.io/en/internal_external_events/https://event-driven.io/en/internal_external_events/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/98bea3c5e670343ff0ff640f9698db92/a331c/2023-10-15-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4gAAAuIAHVHB4bAAACS0lEQVQoz3WSS08TcRTF+QCu/ArGD8DKyErA0mk7M/+hM53Ou2UEpS1gi/KsgpWKKBAEjDH0Qelj/jPTjilNpKaEsKgYY4wu1BUrHxvjI8So0cQ4hkigmJrc1Ul+95zcexpwTt0fwKmIS3EIBs5ptTrKQgudrVX+TsMByWttZDYcXX+2uk6xGXSPhw5WF8VEpGPMzkDwf1hto5TNjTeV6RJCKTi/l8XGqLKsFK5AUjJQRgH1YA1jc6S3nISvKtUk15VxuDXA78ZB6Mxw+Ekh/XVuehvwWh1nwGt2OnMm+Pjt+yHTbC7BMYuzQAgQ8KrdnZu69lJd/gwTn7r7Nmx0dn/FQWyMzTrlzUopbe5Q335yvgsxhMoDHuKcujj3uqT8UOIfZV/Z7s7VgXFWAWIxcn37wzu/aeKbDy9ayQLGQZeo3YxUw4PGcGgV5XScg3UOhjEKLReD0e8PHummif/67R6N3m5tL7AeFXP0H2sEpxyThHgPZ+vBKAtdnvzVka1bk5UvO6Jpks9f9KKMivP5JmT8REtfK5glditwGAY8xFjV5tZpLrfgn4tKd0rGpa2ngBZOt6AzTqnYZL180jLcjM8QgoHVOqOMhrjyhADbJcUrpyd6lyYC8U45fryRPnK00eqMAd7AyCk7ccNBzgO+cMiZ60oPRRa7+2NnQ0vBkeTUrD6/YNxNrHkCid6B8XOhBclXjseqg6P3B8JrVioH+Jo/EwLs6El6A8tCd0r0pST/iqdnRe5Le/ypzvNZpwQJQQNiHgg6Iej/dPsPj/x0i6qPp38AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/98bea3c5e670343ff0ff640f9698db92/a331c/2023-10-15-cover.png" srcset="/static/98bea3c5e670343ff0ff640f9698db92/36ca5/2023-10-15-cover.png 200w, /static/98bea3c5e670343ff0ff640f9698db92/a3397/2023-10-15-cover.png 400w, /static/98bea3c5e670343ff0ff640f9698db92/a331c/2023-10-15-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>One of the things that we’re learning too late in the Event-Driven approach is that we should have been splitting events into internal and external.</p> <p><strong>While starting our journey, we focus on modelling our business case.</strong> That’s fine, as that’s our team’s bread and butter. Quite often, we’re so laser-focused that we forget the bigger picture. That blissful ignorance can last long, but at some point, one of our colleagues will tap our back and ask:</p> <blockquote> <p><em>“Hey, we need data from you. Could you expose it?”</em>.</p> </blockquote> <p>Then, the first thought is:</p> <blockquote> <p><em>“Easy peasy, we’ll just republish our events.”</em></p> </blockquote> <p>And then we answer:</p> <blockquote> <p><em>“No worries, I’ll expose you that event.”</em></p> </blockquote> <p>Let’s say that we’re building a shopping cart module (read more on it in <a href="how_to_effectively_compose_your_business_logic/">How to effectively compose your business logic</a>). The colleague who tapped our back is working on the payment module. They need to start the payment process on shopping cart confirmation.</p> <p><strong>If we’re already using messaging tooling (e.g. Kafka, RabbitMQ, etc.), the easiest option is to say:</strong></p> <blockquote> <p><em>“Just subscribe to this topic and listen to ShoppingCartConfirmed event.”</em></p> </blockquote> <p>Thanks to that, no additional work is needed. Other modules will get all the data from us, trigger their processes, or build read models.</p> <p>That can work for some time, especially if we’re good colleagues. But sooner or later, we’ll get the feedback:</p> <blockquote> <p><em>“That’s great that you exposed me to the event, but it only contains information on when the shopping cart was confirmed. I need to know the client and the total amount of all products. Could you add that information to the event?”</em></p> </blockquote> <p>After hearing that, we may agree and extend event data or say that we already publish information about the client and products in the earlier events. We may say:</p> <blockquote> <p><em>“Just collect data from other events. Take the client id from ShopingCartOpened event, and sum amounts from ProductItemAdded events.”</em></p> </blockquote> <p>If our colleagues are not assertive or have a tight schedule, they may agree, which can work for some time. But then they will come back and say.</p> <blockquote> <p><em>“Hey, that worked fine for some time, but we’re getting discrepancies, and client complain that from time to time we’re charging them too much. Could you investigate it?”</em></p> </blockquote> <p>We put on our detective hats, investigate what happened and end with the conclusion:</p> <blockquote> <p><em>“What a moron!</em></p> </blockquote> <p>Then we contact our colleague and say:</p> <blockquote> <p><em>“Hey, you should also have handled ProductItemRemoved. Users can not only add but also remove items from the shopping cart.</em></p> </blockquote> <p><strong>If we reach that point, we should stop and do a sanity check.</strong> We may conclude that we were right in shouting <em>“What a moron!”</em>, but the target was wrong. Our colleague doesn’t need to know about the details of our implementation. What’s more, they should not know the details of our process. If they have to, then we have leaking abstractions.</p> <p>I wrote about that issue in detail in <a href="/pl/events_should_be_as_small_as_possible/">Events should be as small as possible, right?</a>. If we expose our internal events, we must communicate and consult each change we make with other teams. We have to assume that the event is being used by someone else and that they may need to use the new one.</p> <p><strong>So, the easiness of just exposing everything will bite us hard.</strong></p> <p>Should we extend our events and make them bigger then? Yes and no.</p> <p><strong>We should provide the <a href="https://verraes.net/2019/05/patterns-for-decoupling-distsys-summary-event/">Summary Event</a> that will provide the <a href="https://verraes.net/2019/05/patterns-for-decoupling-distsys-completeness-guarantee/">complete information</a> needed for other modules.</strong> If they also need information about other steps of the process, we can have more than one summary event.</p> <p>Summary Event can be easily mistaken with <a href="https://codeopinion.com/event-carried-state-transfer-keep-a-local-cache/">Event Carried State Transfer</a> or <a href="https://www.eventstore.com/blog/snapshots-in-event-sourcing">Snapshot</a>, yet they’re not the same. Summary Event still gathers business information about the business fact that has happened. We can not only replicate the state (as with Event Carried State Transfer) but also trigger the workflow’s next steps.</p> <p><strong>Why am I not using <em>domain event</em> and <em>integration event</em> terms?</strong> Because they’re highly misleading. All of them should be domain events. They are just used in different contexts. Internal (or private) is understandable in the module context, and external (or public) is understandable in the whole system context. If you’re doing EventStorming sessions, external are those we find during big-picture discussions and internal at the design and process level analysis.</p> <p>Let’s get back to our colleague’s request:</p> <blockquote> <p><em>“Hey, we need data from you. Could you expose it?”</em>.</p> </blockquote> <p>We already know that’s not as <em>easy peasy</em> as we thought. Instead of downplaying the case, we should start by asking:</p> <blockquote> <p><em>“I’m open to that, but before we agree on how to technically solve it. Could you explain me your need and business scenario?”</em></p> </blockquote> <p>So we should ask our colleague to <a href="/pl/bring_me_problems_not_solutions/">bring us the problem instead of the solution</a> and understand their use case.</p> <p><strong>We can then define our API.</strong> Yes, API. Events should be treated as such. We need to understand:</p> <ul> <li>how many other modules will have a similar need,</li> <li>is data needed to get a local copy of our data or to trigger the workflow,</li> <li>how much data we’ll need to expose,</li> <li>is data personally identifiable information,</li> <li>etc.</li> </ul> <p>All of that should give us enough context on how to proceed.</p> <p><strong>We can also use tools like <a href="https://www.youtube.com/watch?v=k5i4sP9q2Lk">Context Maps</a>;</strong> they should help us define the dependency between our modules if our module is downstream or upstream. In other words, should we think of our data as generic and supporting to others or the core of our business? It is essential to understand how we will distribute and enrich our data.</p> <p><strong>Let’s say our information is generic enough, and many modules will need a similar data set.</strong> Then, in our messaging system, we can define two distribution channels (e.g. topic in Kafka or SNS, fan out in RabbitMQ, etc.):</p> <ul> <li><em>shopping_carts-internal</em> - for modules that are part of our context or managed by our team. They can get our internal, granular events, as we already need to know the internal details.</li> <li><em>shopping_cart-external</em> - for all other external modules. That will contain bigger, <em>enriched</em> summary events.</li> </ul> <p>If our internal event looks as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> ConfirmedAt <span class="token punctuation">)</span></code></pre></div> <p>Then our enriched could look:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">namespace</span> <span class="token namespace">ShoppingCarts<span class="token punctuation">.</span>External</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> TotalAmount<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> FinalizedAt <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We added all information about the selected product, total amount, etc. TotalAmount looks redundant. We could, in theory, tell one to calculate it from product item information. Yet, remember that we don’t want to expose the business logic and need to repeat computations for all consumers. Price calculation can be complex if we consider discounts, loyalty plans, taxes, etc. We should keep it cohesive and do it in the module that’s the source of truth for that business process. It is a decent example that a healthy amount of copy and paste won’t harm.</p> <p><strong>The example code doing enrichment could look like this:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">HandleCartFinalised</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span>EventEnvelope<span class="token punctuation">&lt;</span>ShoppingCartConfirmed<span class="token punctuation">></span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ShoppingCartConfirmed<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellationToken<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> cart <span class="token operator">=</span> <span class="token keyword">await</span> querySession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AggregateStreamAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>CartId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">version</span><span class="token punctuation">:</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Metadata<span class="token punctuation">.</span>StreamPosition<span class="token punctuation">,</span> <span class="token named-parameter punctuation">token</span><span class="token punctuation">:</span> cancellationToken <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cart <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> externalEvent <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventEnvelope<span class="token punctuation">&lt;</span>External<span class="token punctuation">.</span>ShoppingCartConfirmed<span class="token punctuation">></span></span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CartFinalized</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>CartId<span class="token punctuation">,</span> cart<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> cart<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> cart<span class="token punctuation">.</span>TotalPrice<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ConfirmedAt <span class="token punctuation">)</span><span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Metadata <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> externalEventBus<span class="token punctuation">.</span><span class="token function">Publish</span><span class="token punctuation">(</span>externalEvent<span class="token punctuation">,</span> cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IQuerySession</span> querySession<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IEventBus</span> externalEventBus<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">HandleCartFinalized</span><span class="token punctuation">(</span> <span class="token class-name">IQuerySession</span> querySession<span class="token punctuation">,</span> <span class="token class-name">IExternalEventBus</span> externalEventBus <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>querySession <span class="token operator">=</span> querySession<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>externalEventBus <span class="token operator">=</span> externalEventBus<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>It’s a simple event handler that:</strong></p> <ol> <li>Takes the internal event.</li> <li>Loads additional data.</li> <li>Builds enriched external events from internal events and loaded data.</li> <li>Forwards it to messaging tooling.</li> </ol> <p>Event handler can be triggered by <a href="/pl/outbox_inbox_patterns_and_delivery_guarantees_explained/">outbox</a>, <a href="en/integrating_Marten/">subscription</a> or your preferred messaging tooling. Event bus can store the message in the outbox to forward it later or just push it to your favourite messaging system.</p> <p>If you’re using Event Sourcing, you can append an event to the dedicated external stream (per shopping cart or per module, depending on your needs) and then forward it via subscription to the messaging system.</p> <p><strong>I’m using Marten’s <em>AggregateStreamAsync</em> method that <a href="/pl/how_to_get_the_current_entity_state_in_event_sourcing/">builds state from event</a> for loading.</strong> That’s a big benefit of event sourcing, as I can pass the internal event stream position and get the state at a specific point in time. If I used a regular approach, I may face race conditions related to eventual consistency. The event handler could be triggered after there was another state change. Then, if I load it, I would get too recent state. It could mean that we’re trying to enrich the event about shopping cart confirmation but getting cancelled if someone did that in the meantime. It doesn’t have to be a big deal if our Summary Event is a final one, but that’s something that we should keep in mind.</p> <p><strong>We can use precisely the same techniques if our module is not generic but supportive.</strong> The difference could be that our API should be then more like <em><a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/backends-for-frontends">Backend for Frontends</a></em> and we publish more events fine-tuned for different customer needs. For instance, besides the internal channel, we could have two external channels:</p> <ul> <li><em>shopping_cart_finance-external</em> - for all other modules needing information about financial aspects of the buying process,</li> <li><em>shopping_cart_products-external</em> - for those modules that need information about products, e.g. shipment.</li> </ul> <p>Then, we could have two different external events:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">namespace</span> <span class="token namespace">ShoppingCarts<span class="token punctuation">.</span>External<span class="token punctuation">.</span>Finances</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> TotalAmount<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> FinalizedAt <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">namespace</span> <span class="token namespace">ShoppingCarts<span class="token punctuation">.</span>External<span class="token punctuation">.</span>Products</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> FinalizedAt <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then, we get more information to fulfil conflicting module requirements and provide more precise events.</p> <p>Of course, we should be careful and not try to send <em>passive-aggressive events</em>, so events that should be commands. If we know that we’ll always have a single consumer for an event that always needs to run the specific logic and expect to get the particular event back, then it should probably be a command. Read more in <a href="/en/whats_the_difference_between_event_and_command/">What’s the difference between a command and an event?</a>.</p> <p>How do we document our Events API? That’s currently a bit of a challenge; there are initiatives like <a href="https://cloudevents.io/">Cloud Events</a> and <a href="https://www.asyncapi.com/">AsyncAPI</a>, they provide a description format, but they’re not yet a global standard as e.g. Open Telemetry.</p> <p>Tools like <a href="https://www.eventcatalog.dev/">EventCatalog</a> allow us to design and view our events. Yet, also regular Markdown is good enough if we organise that together with our code.</p> <p><strong>The most important aspect is to think about events as API.</strong> We should understand that we’ll shoot ourselves in the foot if we don’t split our events into internal and external. We’ll have a leaking abstraction that creates coupling, and it’s a first step to the distributed monolith. I’ve been there; it’s a dark place that I don’t recommend.</p> <p><strong>I hope this article will show you that simple techniques for discussing the API and enriching events can take you far and help create maintainable systems.</strong></p> <p>Check also more in:</p> <ul> <li><a href="/en/events_should_be_as_small_as_possible/">Events should be as small as possible, right?</a>,</li> <li><a href="https://verraes.net/2019/05/patterns-for-decoupling-distsys-explicit-public-events/">Mathias Verraes - Explicit Public Events</a>,</li> <li><a href="https://www.youtube.com/watch?v=qf-BSAhbrWw">Derek Comartin - Event-Driven Architecture Gotcha! Inside or Outside Events</a>,</li> <li><a href="https://www.youtube.com/watch?v=Pph8TFPOfko">Marc Klefter - Powering Event-Driven APIs with Event Sourcing</a>,</li> <li><a href="https://queue.acm.org/detail.cfm?id=3415014">Pat Helland - Data on the Outside vs. Data on the Inside</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to design software architecture pragmatically]]>https://event-driven.io/en/how_to_design_software_architecture_pragmatically/https://event-driven.io/en/how_to_design_software_architecture_pragmatically/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/80dbbcb1cd07a02254a83a1b05b6b006/a331c/2023-10-06-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4gAAAuIAHVHB4bAAADJElEQVQozwEZA+b8AMHGzq6wuoCNoY6WprqYmsajsrGktIWFjzw8Ra+0vaWmsIx4fKWNnvP1++zx8q2lsdDK1uLk5dC/ujMvLQC7sLm/tMKdm6RXUVRyZWV0cmwmJihgX2uDiJWhnK+Aa1OAeC2vrKfb4Ob0+Pm1pbC+sL3ZxdDLs8FFOjwAybTBb2Vtrpelvrm9ZW1tVT40lXVjREZKKy01RD1FvrGhwK+HkoR/o6Ck1tbVsre1xMG9xayyioyLODQ4AJ2hpmtwc7avv+bW43BmX31aT4RjV1U9R25lbqufpsTF0nBwe8G0uY95caioq5GWiaWjn7ekrpGCiGtsbwCDhYydoa3x+f+MiIZCOjtBNTNOOzu3pLjVzt3X1d/IrbKhkIaTfnWUjpDLz9agopklKx56gHzs7uxrb3AAr6umzcrGrZ6bg35/wc/iPUZSaXF9vMzemqe7o6WufmFUd19bqaWvucDNvsHIioyAZXRXoaqgv8K/ZmprALCqp3NcRj0wLHqFk83Z6k5QVIiQnZSdrZaisqqptLSZp7+1x6eptqirrru/s7u9stLU1MvM08HEwaquqQBMTVYSDQtES1ZSXW1teIk+PDtpbXbHy9Dj4uDEyMLRw8XMxtTKzdisrq2jppidoJilqJ+wtam2t7CqsKgAb10ybGBCZm+AdYKTb3uLGxobZmdpj5KVe3x5jpCHsbapxsu+xMW+paefubyua2xpoaSXx8i6r7iuepaDAJCFbHx3bo+brHaDlml0hB4bGWVlY5mcnIiLiZaYjpaYjZuckq6wpr/DtLq9rY2QlqexroegjGeKdVN5YgAlKSRSWFigr8ajsMNyfo8TERFNTUyWmZiEh4SNkYxfYl9TVVFISkRgY128wLOJnpJjiHNZf2hTdmFSdWAANDYpPT81PkRKRUhMKy8zDg8OU1VSenx6e316bXFxX2JdYGJaTlBKLC0q3N7b8PXueZKAS3BaUXVgTXFcAC4wJkFDN19dT1lXSSUlHS4uJTc4MCAhHissKC8xL2BjXYGEezw/Ok9RUu7w6fPy6tXZz1d2Y05yXUxwW0PFohBKlH0eAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/80dbbcb1cd07a02254a83a1b05b6b006/a331c/2023-10-06-cover.png" srcset="/static/80dbbcb1cd07a02254a83a1b05b6b006/36ca5/2023-10-06-cover.png 200w, /static/80dbbcb1cd07a02254a83a1b05b6b006/a3397/2023-10-06-cover.png 400w, /static/80dbbcb1cd07a02254a83a1b05b6b006/a331c/2023-10-06-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I’ve run numerous <a href="/en/training/">workshops</a> in recent years. It’s intriguing to see different ways people solve the same problem.</strong> Some start from general vision and go into details, some the other way.</p> <p>Some focus on making the design look nice and tidy on the drawing board and strictly following design methodology. Others do not care about the form and rules of the tool used and just <em>let it fly</em>.</p> <p>Each approach has advantages and disadvantages. I want to share some conclusions based on my practice and how I work with modelling, design, etc.</p> <p><strong>The first stage of working with a completely new functionality is the creative stage.</strong> We don’t know much about the problem and tooling stack. We find dots and connect them. A suitable method here is good old brainstorming.</p> <p>Here are a few rules for making it effective:</p> <ul> <li>there are no stupid ideas,</li> <li>we don’t criticize; we focus on our ideas, not other people’s,</li> <li>we generate as many ideas as possible,</li> <li>when we run out of ideas, we should still try to grind a bit longer and tell ourselves to generate 2-3 more. It’s like running; often, the best results occur when we pass the first fatigue threshold,</li> <li>however, it is important to set a maximum duration for brainstorming. It should be an intensive and productive meeting. There’s no point in making it an endless one. You can always organize several sessions.</li> </ul> <p><strong>That is why tools such as <a href="https://www.eventstorming.com/">EventStorming</a> and other <em>sticky-notes-based</em> tools are so popular.</strong> They allow us to throw our ideas onto the wall, crunch them and stimulate discussions.</p> <p>Having a good mixture of people at such a meeting is essential. We should have people of all sorts: programmers, business people and testers. We’re discussing the general vision at the early stages here, so every perspective counts.</p> <p><strong>Yet, it’s also essential to not have too many people and care for mental health.</strong> Doing continuous brainstorming sessions can be draining and ineffective. We need to let our thoughts sink in. I’m personally an introvert, and I’m emotionally exhausted after a set of longer sessions and need time to recover (read more in <a href="/en/agile_vs_introverts/">Agile vs Introverts</a>).</p> <p>Thus, after brainstorming, it is worth sifting out what we have found. We should group ideas by category. Even if some look like duplicates, discussing them before we throw them out is crucial. Often, such details contain grey area, so details that can have a significant impact but are easy to miss.</p> <p><strong>The brainstorming itself may concern specific functionalities as well as the global vision of the system.</strong> EventStorming divides this into <em>“big picture”</em> and <em>“process/design level”</em>. First, we look at the events that are essential from the overall system perspective, then we go lower (read more about the distinction between internal and external events on my blog: <a href="/en/events_should_be_as_small_as_possible/">“Events should be as small as possible, right?”</a>).</p> <p><strong>Although the <em>“big picture”</em> indicates that in EventStorming, we move from general to specific, we also go the other way round.</strong> The starting point is finding events; they are granular and precise by definition. We’re shaping the business workflow by grouping and placing them in order. We can also find boundaries, language nuances and define <a href="https://martinfowler.com/bliki/BoundedContext.html">bounded contexts</a>. Having that, we can go down the rabbit hole again, breaking down processes into smaller functionalities.</p> <p><strong>Another technique that helps you move from general to specific is the good old <a href="https://en.wikipedia.org/wiki/Mind_map">Mind Map</a>.</strong> Starting from the main block, which may represent, for example, our system, we can break it down into details - e.g. answering the question <em>“what do I need to have to build this system?”</em>. Having general answers and overall ideas, we can ask similar questions on those answers and find more details.</p> <p>Such an approach has a name: <a href="https://en.wikipedia.org/wiki/Five_whys">“5 whys”</a> method. We can ask the business more questions, why, to find out the actual source of the problem. I detailed that in <a href="/en/bring_me_problems_not_solutions/">Bring me problems, not solutions!</a>.</p> <p><strong>Those tools are great but often difficult to translate directly into implementation.</strong> I had many cases when people finished their brainstorming sessions, having distilled their current process and were super proud of what they did. Unfortunately, I broke the charm by asking them: <em>“ok, what’s next?”</em>. Usually, the answer was surprised the <em>isn’t-that-already-the-end?</em> look.</p> <p>Nope, it’s not the end. The result of brainstorming is just an entry point for the proper system design. As I outlined in <a href="/en/dont_let_event_driven_architecture_buzzwords_fool_you/">the other article</a>, the design flow should look like this:</p> <ol> <li><strong>WHAT?</strong> Describe and model the business workflow. Understand the product requirements and <a href="/en/follow_the_money/">follow the money to find why they’re important from the business perspective</a>.</li> <li><strong>HOW?</strong> 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, <a href="https://www.enterpriseintegrationpatterns.com/">integration patterns</a>, not the specific technologies.</li> <li><strong>WITH.</strong> 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, etc.</li> </ol> <p>Brainstorming is at the WHAT level. We still need to understand HOW and WITH.</p> <p>What design tools can help us to move further?</p> <p><strong>An interesting one is <a href="https://c4model.com/">the C4 model</a>.</strong> which allows us to zoom in and out perspective on our system. We have four contexts:</p> <ul> <li>System: all the internal and external systems involved in our solution.</li> <li>Containers: all of our deployment units, so everything we need to take care of, deploy, monitor etc. That includes both services and tools like messaging tooling, databases, etc.</li> <li>Components: now we look deeper and see the components of the specific deployment units. We can see modules here and the logical split of the bigger building blocks.</li> <li>Code - all the code structures, implementation patterns, communication between them, etc.</li> </ul> <p>We start with a general vision of the system and go lower and lower. Thanks to this, by dividing our architecture into individual levels, we create diagrams that are clear in a given context and allow us to understand what our system does and see how. Read also more on what factors to take into account in <a href="/en/how_to_cut_microservices/">How (not) to cut microservices</a>.</p> <p><strong>I typically prefer a general-to-detail approach, but I also always <em>drill test boreholes</em>.</strong> By that, I’m setting tunnel vision on key functionalities, verifying the hypothesis, and ensuring what we thought would work in practice. It is an excellent place for <a href="/en/prototype_underestimated_design_skill/">prototyping, an underestimated design skill</a>. You can do both purely technical spike and implementing business models. We can confront both technical and business assumptions.</p> <p>Running such spikes is essential because if we do not do it, we risk that some functionality will become a dead end, and the overall design will have to be changed because of it.</p> <p><strong><a href="/en/why_are_senior_devs_afraid_to_code/">Are you afraid to code?</a> there’s another tool you can use, right on paper: <a href="https://monday.com/blog/project-management/risk-register/">Risk Register</a>.</strong> I explained it in <a href="/en/the_risk_of_ignoring_risks/">The risk of ignoring risks</a>. The risk register is a simple table. We write down all risks for our solution. 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, then it’s better to change our way. If the risk is unlikely with little consequence, we can ignore it. Usually, however, it is somewhere in the middle. We should write down what we will do when the risk occurs. Thanks to that, we can find the threats more easily and define strategies to deal with them or change design to make it more resilient. <a href="/en/why_are_we_afraid_of_our_decisions/">We should not be afraid to make decisions</a>, this is the tool that can help us with that.</p> <p>Other useful, low-level tools are UML <a href="https://en.wikipedia.org/wiki/Sequence_diagram">Sequence</a> and <a href="https://en.wikipedia.org/wiki/Activity_diagram">Activity</a> diagrams. They allow you to describe a specific flow in more detail. Keeping them as small and focused as possible is critical to keep them readable. You can always make several for the same functionality, showing different variants (e.g. happy path, error flow, etc.).</p> <p><strong>Of course, I described only sample tools above; there are plenty of them. Perhaps too much.</strong> They have become surrounded by consultants who make the business by teaching them. When reading some materials, you could think that using a modelling methodology and creating a design is enough, and the code will write itself. That’s obviously not the case.</p> <p>Common sense and adapting the tools to my needs are important for me. I sometimes do sketches on paper instead of specific methodologies. I like to create blocks in an application like <a href="https://www.drawio.com/">draw.io</a>. They may not be the most maintainable in the long term, but they can provide a good entry point for discussion or <a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">immutable decision record</a>.</p> <p><strong>Maintainability of the outcome of design artefacts is also an essential aspect of those methodologies.</strong> Some are better for discussion, and some are static documentation. Brainstorming tools are rarely maintainable; they’re designed to trigger discussions and creativity, which often is chaotic. You will probably not be able to keep the EventStorming model in Miro up to date, but you should be able to do that with the C4 model (especially with levels 1-3; 4th could be generated from code).</p> <p>For group discussions and brainstorming, I recommend using ready-made methods with specific notations (e.g., EventStorming, C4). They eliminate discussions on what notation we’ll use, what’s this or that. It helps to focus on the merit, so design discussions instead of trying to understand what this box means for other people.</p> <p>Of course, we are not always able to arrange group sessions. Not every organisation allows to do that on a regular basis. However, we should still do it. We can always do design sessions within the team; if our team is unwilling, we can start with ourselves. We should not stop ourselves from visualising, writing down and prototyping our design. It is part of our job and responsibility as engineers. If it helps us, we can show the result to others. It will be easier to convince others to do so once they see it working.</p> <p><strong>What if you don’t know how to do it? Just try it.</strong> The first times are usually clumsy. The ability to model a system is a skill like any other. It requires repetition and practice. It will get easier with each subsequent attempt.</p> <p>It’s also easier now. Many tools support these days collaborative design and popular methodologies. They make it easier to catalyze discussions between teams.</p> <p>You can also make a dry run with your teams doing <a href="https://nealford.com/katas/list.html">Architecture Katas</a> as a form of team-building exercise.</p> <p><strong>The best time to start is not tomorrow but now.</strong></p> <p>How do you approach such topics?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Is the Strategy Pattern an ultimate solution for low coupling?]]>https://event-driven.io/en/is_strategy_pattern_an_ultimate_solution_for_low_coupling/https://event-driven.io/en/is_strategy_pattern_an_ultimate_solution_for_low_coupling/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e1764ada3a11425bce8089e83509e760/a331c/2023-10-01-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4hAAAuIQEHW/z/AAADJElEQVQozwEZA+b8AAAAACkwREtNZkgvMysdICogJjUhJjIZICMXHTkxN1NHWE8vNBgZITEqNTk5TjZAWWVri09HV0pATiwrOQAKAAALDxcfHigpGRoZEhYKDBBXRVVmT1qnf3LGl4GigIcyFBwKDxcWHCk2OU0yKTMgICtgQEE6Mj0ZGSEAEgQHJBcaRy4vJx0hFg4RAwYKRzlFvZWM0Z+QuoFqnHdrCAoRCgoQJy9CMTZJRickFBAUVElYaXCRHCMzABURGVJKWlVdfVNNYQgLECANE0EqM6h8bMeHZ76Fb5Z2cAcMEwUHDFxMWm56nm1vijMpMkA3SlRggR0fKwAaHipJSFtFSGI2LzsFCxMjGioYEihkUlNzRjRpQjtPOjoABw8pHiNPPkl2dZVkbI1ALDFMKDInGiEbGCEAOyctQlJycFprQ0BSEBQfKCMtNCQwonVmhVQ8QSwoIxsgFhUfSTQ8U01gUkRQHxkfKR8kTC8yJA0QOxseACkcIlpeeZCVtmpgcpF8fpV2eXFLW711b6RkRcJ1ZXBSZlxBSGtaYTY0Pk8+RUMfJhoTF0AoLTkEDDwbHwATERkwLjuCdI2oiImhgHSUXF1GPUtBNkOPXWeZbnd9Ymy5dW+6hni3m5qEbXJUKi9RNTo7HCEmBAskFxsADQ4VNyYrfFhdmH5+j3FqdVFSa1lbY1JRnXZxzIh9yHZqz3t1v3Zt3q2l5ebulpGXVUtQAwMEDxAXRzM3ACsmLnp4jquSmqB+eo1rYo9qYKl2ZZ6BeoN4fLmhoruhoaZ2epxXS6RxYOrVy/z///P4/5WYnkExOhwfJgBBLDFxeJOmhoqCTzuUVT9sQC46MjBHTFY7Q05nbHd3f4o7PESGPkBuUV1iQjjFppvx7vH///9/Xm00DiEAYjg5RCstYV1vj4GNr2ZriRcgCgAHBQ0VAAEIaFddbVheAAYPQy84PTE7CgoUYz9DupKPpYOBb1BLqFRbADwoK0s3O1Q5PmdJTqShp8tyeXsBCCQQFgAAAWBGRFVBPwAABjw3RF5CR2BHTVVKVVw7RUgbID0qLGhCSrRc8rcjObHSAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e1764ada3a11425bce8089e83509e760/a331c/2023-10-01-cover.png" srcset="/static/e1764ada3a11425bce8089e83509e760/36ca5/2023-10-01-cover.png 200w, /static/e1764ada3a11425bce8089e83509e760/a3397/2023-10-01-cover.png 400w, /static/e1764ada3a11425bce8089e83509e760/a331c/2023-10-01-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Having a single source of truth and <em>data point</em> is a sweet spot for running business logic.</strong> We check the data from one place and update it. Easy peasy. The challenge is when we become a juggler and run in based on multiple sources.</p> <p><strong>I’m a sports fan. One of the most exciting things about being a fan is trade rumours.</strong> Last week’s NBA fans were interested in where Damian Lillard will be traded. Most were saying that he’ll go to the Miami Heat, others noted that the Toronto Raptors are interested, and others claimed that he’ll stay in Portland. Trail Blazers fans were probably refreshing the news, hoping the last part would become true and their idol wouldn’t go anywhere. And then boom, <a href="https://www.espn.com/nba/story/_/id/38506274/damian-lillard-traded-bucks-big-questions-most-shocking-deal-nba-offseason">he was traded to the Milwaukee Bucks</a>.</p> <p><strong>Now, it seems that most journalists were wrong; some were close to the truth but not quite. The final consistency and the source of truth was the Portland Trail Blazers that finally made the move.</strong> Still, they also needed the data from outside, so agreement with Milwaukee Bucks. They had to trust that Milwaukee wouldn’t change their mind and confirm the trade to the NBA to accept the trade formally.</p> <p>The same happens in our code. We should try to make the decision-making as autonomous as possible, but that’s hard to achieve in real life.</p> <p><strong>What if we’d like to build an application like <a href="https://basketball.realgm.com/tradechecker/">NBA Trade Checker</a>?</strong> If we’d like to model that in code, trade offer could look as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">record</span> <span class="token class-name">PlayerContract</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> PlayerId<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> ValidFrom<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> ValidTo<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Amount<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> CanVoteTrade <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">TeamOffer</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> TeamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> PlayerContracts <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">BilateralTradeOffer</span><span class="token punctuation">(</span> <span class="token class-name">TeamOffer</span> Gives<span class="token punctuation">,</span> <span class="token class-name">TeamOffer</span> Gets <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">TradeOffer</span><span class="token punctuation">(</span><span class="token class-name">BilateralTradeOffer<span class="token punctuation">[</span><span class="token punctuation">]</span></span> TeamsOffers<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token function">PlayersToGive</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">)</span> <span class="token operator">=></span> TeamsOffers <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>t <span class="token operator">=></span> t<span class="token punctuation">.</span>Gives<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>t <span class="token operator">=></span> t<span class="token punctuation">.</span>TeamId <span class="token operator">==</span> teamId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>t <span class="token operator">=></span> t<span class="token punctuation">.</span>PlayerContracts<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token function">PlayersToGet</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">)</span> <span class="token operator">=></span> TeamsOffers <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>t <span class="token operator">=></span> t<span class="token punctuation">.</span>Gets<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>t <span class="token operator">=></span> t<span class="token punctuation">.</span>TeamId <span class="token operator">==</span> teamId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>t <span class="token operator">=></span> t<span class="token punctuation">.</span>PlayerContracts<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The implementation of the trade could look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">TeamRoster</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Guid</span> teamId<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">TeamRoster</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>teamId <span class="token operator">=</span> teamId<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>currentContracts <span class="token operator">=</span> currentContracts<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">TradePlayers</span><span class="token punctuation">(</span><span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> maxSalaryCap<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGive <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGive</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playersToGive<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>currentContracts<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Offer cannot include players that are not in the team roster"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGet <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGet</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newRoster <span class="token operator">=</span> currentContracts <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>e <span class="token operator">=></span> <span class="token operator">!</span>playersToGive<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>playersToGet<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>newRoster<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>c <span class="token operator">=></span> c<span class="token punctuation">.</span>Amount<span class="token punctuation">)</span> <span class="token operator">></span> maxSalaryCap<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"New roster contracts cannot go above maximum salary cap"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> currentContracts <span class="token operator">=</span> newRoster<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>The implementation is quite naive; to make it production-ready, we’d need to either model the full <a href="/en/saga_process_manager_distributed_transactions/">distributed process</a> or at least <a href="/en/simple_transactional_command_orchestration/">orchestrate changes to multiple teams as batch</a>. Yet, let’s focus today on dealing with the complex, changing requirements.</p> <p>So far, trade logic looks simple; you select teams, then players, and ensure such a trade can happen. Still, it’s not that simple. Contracts need to match; the team’s overall salary cannot go above a certain level, some players can have trade veto in their contract, etc. What’s more, those rules tend to change. Even to have a look at the <a href="https://www.cbssports.com/nba/news/nba-cba-101-everything-to-know-about-new-agreement-from-salary-cap-to-free-agency-and-beyond/">recap of this year’s changes</a> takes a lot of time to process that.</p> <p>Let’s say that we’d also like to model player veto. We could add player’s preferences and include them in our validation logic:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">record</span> <span class="token class-name">PlayerVetoPreferences</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> PlayerId<span class="token punctuation">,</span> <span class="token class-name">Guid<span class="token punctuation">[</span><span class="token punctuation">]</span></span> AcceptableTeamsAsTradeDestination <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">TradeOffer</span><span class="token punctuation">(</span><span class="token class-name">BilateralTradeOffer<span class="token punctuation">[</span><span class="token punctuation">]</span></span> TeamsOffers<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> <span class="token function">TradeDestination</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> playerId<span class="token punctuation">)</span> <span class="token operator">=></span> TeamsOffers <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>t <span class="token operator">=></span> t<span class="token punctuation">.</span>Gets<span class="token punctuation">.</span>PlayerContracts<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>c <span class="token operator">=></span> c<span class="token punctuation">.</span>PlayerId <span class="token operator">==</span> playerId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>t <span class="token operator">=></span> t<span class="token punctuation">.</span>Gets<span class="token punctuation">.</span>TeamId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">TradePlayers</span><span class="token punctuation">(</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> maxSalaryCap<span class="token punctuation">,</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> PlayerVetoPreferences<span class="token punctuation">></span></span> playersVetoPreferences <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGive <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGive</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playersToGive<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>currentContracts<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Offer cannot include players that are not in the team roster"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> playersWithVetoRules <span class="token operator">=</span> playersToGive<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>CanVoteTrade<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playersWithVetoRules<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playersWithVetoRules<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>playersVetoPreferences<span class="token punctuation">.</span><span class="token function">ContainsKey</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Cannot risk trade for unknown veto preferences"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playersWithVetoRules<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>playersVetoPreferences<span class="token punctuation">[</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">]</span><span class="token punctuation">.</span>AcceptableTeamsAsTradeDestination <span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>tradeOffer<span class="token punctuation">.</span><span class="token function">TradeDestination</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Player will veto the trade"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>We just added basic rules, and the method is already pretty big. We can already see that this method will tend to grow exponentially.</strong> We could try to deal with that by breaking down processing and validation parts into dedicated methods and group parameters into one object. That would make it more understandable but wouldn’t help us deal with the changing trade rules.</p> <p>As mentioned earlier, those rules change regularly and quite often significantly. Yet, the general mechanism from the team roster remains the same:</p> <ol> <li>Ensure that the proposed trade contains players currently under contract.</li> <li>Update the roster by replacing players included in the trade.</li> </ol> <p><strong>That creates an impedance mismatch. The general rules won’t change much, but the validation rules quite often.</strong> If we keep the code responsible for both at the same place, we’ll get a lot of noise in the change management.</p> <p>How to handle that? There are a few options; let’s go through that.</p> <h2 id="injecting-strategy" style="position:relative;"><a href="#injecting-strategy" aria-label="injecting strategy permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Injecting strategy</h2> <p>We could define the following interface:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">interface</span> <span class="token class-name">ICheckTrade</span> <span class="token punctuation">{</span> <span class="token return-type class-name">Result</span> <span class="token function">CanTradeTo</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">enum</span> <span class="token class-name">Result</span> <span class="token punctuation">{</span> Ok<span class="token punctuation">,</span> UnknownPlayerVetoPreferences<span class="token punctuation">,</span> PlayerWillVetoTrade <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>And define the current implementation there:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">TradeChecker</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ICheckTrade</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">NBATradeParameters</span> nbaTradeParameters<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> PlayerVetoPreferences<span class="token punctuation">></span></span> playersVetoPreferences<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">TradeChecker</span><span class="token punctuation">(</span> <span class="token class-name">NBATradeParameters</span> nbaTradeParameters<span class="token punctuation">,</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> PlayerVetoPreferences<span class="token punctuation">></span></span> playersVetoPreferences <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>nbaTradeParameters <span class="token operator">=</span> nbaTradeParameters<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>playersVetoPreferences <span class="token operator">=</span> playersVetoPreferences<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ICheckTrade<span class="token punctuation">.</span>Result</span> <span class="token function">CanTradeTo</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGive <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGive</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> playersWithVetoRules <span class="token operator">=</span> playersToGive<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>CanVoteTrade<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">AreAnyPlayerWithVetoRightsThatHaveUnknownPreferences</span><span class="token punctuation">(</span>playersWithVetoRules<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>UnknownPlayerVetoPreferences<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">WillAnyPlayerVetoTrade</span><span class="token punctuation">(</span>playersWithVetoRules<span class="token punctuation">,</span> tradeOffer<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>PlayerWillVetoTrade<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">IsNewRosterAboveMaximumSalaryCap</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> playersToGive<span class="token punctuation">,</span> currentContracts<span class="token punctuation">,</span> tradeOffer<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>PlayerWillVetoTrade<span class="token punctuation">;</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">AreAnyPlayerWithVetoRightsThatHaveUnknownPreferences</span><span class="token punctuation">(</span><span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>PlayerContract<span class="token punctuation">></span></span> playersWithVetoRules<span class="token punctuation">)</span> <span class="token operator">=></span> playersWithVetoRules<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>playersVetoPreferences<span class="token punctuation">.</span><span class="token function">ContainsKey</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">WillAnyPlayerVetoTrade</span><span class="token punctuation">(</span><span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>PlayerContract<span class="token punctuation">></span></span> playersWithVetoRules<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">)</span> <span class="token operator">=></span> playersWithVetoRules<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>playersVetoPreferences<span class="token punctuation">[</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">]</span><span class="token punctuation">.</span>AcceptableTeamsAsTradeDestination <span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>tradeOffer<span class="token punctuation">.</span><span class="token function">TradeDestination</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">IsNewRosterAboveMaximumSalaryCap</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> playersToGive<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGet <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGet</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newRoster <span class="token operator">=</span> currentContracts <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>e <span class="token operator">=></span> <span class="token operator">!</span>playersToGive<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>playersToGet<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> newRoster<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>c <span class="token operator">=></span> c<span class="token punctuation">.</span>Amount<span class="token punctuation">)</span> <span class="token operator">></span> nbaTradeParameters<span class="token punctuation">.</span>MaxSalaryCap<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>The important part is that the trade checker is self-contained. All external parameters are injected via the constructor, so the calling class won’t need to know the implementation details.</p> <p><strong>Trade checker implements <a href="https://en.wikipedia.org/wiki/Strategy_pattern">Strategy design pattern</a>.</strong></p> <p>We can now inject trade checking strategy as a parameter to the <em>TradePlayers</em> method, reducing its complexity and making it focused on the core logic.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">TeamRoster</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Guid</span> teamId<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">TeamRoster</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>teamId <span class="token operator">=</span> teamId<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>currentContracts <span class="token operator">=</span> currentContracts<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">TradePlayers</span><span class="token punctuation">(</span><span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">,</span> <span class="token class-name">ICheckTrade</span> tradeChecker<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGive <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGive</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playersToGive<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>currentContracts<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Offer cannot include players that are not in the team roster"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> checkResult <span class="token operator">=</span> tradeChecker<span class="token punctuation">.</span><span class="token function">CanTradeTo</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> currentContracts<span class="token punctuation">,</span> tradeOffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>checkResult <span class="token operator">!=</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span>checkResult<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGet <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGet</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newRoster <span class="token operator">=</span> currentContracts <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>e <span class="token operator">=></span> <span class="token operator">!</span>playersToGive<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>playersToGet<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> currentContracts <span class="token operator">=</span> newRoster<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>The service method could look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">TradePlayersHandler</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IAmYourFavouriteDatabase</span> database<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ICheckTrade</span> tradeChecker<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">TradePlayersHandler</span><span class="token punctuation">(</span><span class="token class-name">IAmYourFavouriteDatabase</span> database<span class="token punctuation">,</span> <span class="token class-name">ICheckTrade</span> tradeChecker<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tradeChecker <span class="token operator">=</span> tradeChecker<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>database <span class="token operator">=</span> database<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> teamRoster <span class="token operator">=</span> <span class="token keyword">await</span> database<span class="token punctuation">.</span><span class="token function">GetById</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> teamRoster<span class="token punctuation">.</span><span class="token function">TradePlayers</span><span class="token punctuation">(</span>tradeOffer<span class="token punctuation">,</span> tradeChecker<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> database<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> teamRoster<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We removed impedance mismatch by that. Now, we can just change or even provide a new checking strategy, keeping both business logic and application service intact.</p> <p>Can we call it a day, then? We could, but hold your horses and discuss that a bit more.</p> <h2 id="is-using-strategy-always-a-good-strategy" style="position:relative;"><a href="#is-using-strategy-always-a-good-strategy" aria-label="is using strategy always a good strategy permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Is using strategy always a good strategy?</h2> <p><strong>Let’s have a look again at our trade checker. What it does is the pre-condition check.</strong> We’re injecting it just to validate criteria, passing the internal aggregate state, but not using the result for anything other than validation. Of course, we could say that all is fine; our aggregate drives the business logic, so it should also ensure that all validation was made.</p> <p>Yet, our intention was not to move just code from one place to another but to keep business logic focused and unrelated to rapidly changing conditions. We still have a direct connection here; the aggregate needs to know about the external world.</p> <p><strong>For me, strategies that don’t return any result that’s used by business logic are always a design smell that we should investigate.</strong> They’re not necessarily bad but can indicate coupling and leaking abstractions.</p> <p>How can we do it differently? We can move the strategy outside and call it in the application service.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">TradePlayersHandler</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IAmYourFavouriteDatabase</span> database<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ICheckTrade</span> tradeChecker<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">TradePlayersHandler</span><span class="token punctuation">(</span><span class="token class-name">IAmYourFavouriteDatabase</span> database<span class="token punctuation">,</span> <span class="token class-name">ICheckTrade</span> tradeChecker<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tradeChecker <span class="token operator">=</span> tradeChecker<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>database <span class="token operator">=</span> database<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> teamRoster <span class="token operator">=</span> <span class="token keyword">await</span> database<span class="token punctuation">.</span><span class="token function">GetById</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> checkResult <span class="token operator">=</span> tradeChecker<span class="token punctuation">.</span><span class="token function">CanTradeTo</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> teamRoster<span class="token punctuation">.</span>CurrentContracts<span class="token punctuation">,</span> tradeOffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>checkResult <span class="token operator">!=</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span>checkResult<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> teamRoster<span class="token punctuation">.</span><span class="token function">TradePlayers</span><span class="token punctuation">(</span>tradeOffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> database<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> teamRoster<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see, we also had to publicly expose some needed external state. That’s fine to do if it’s just a getter that doesn’t allow external modifications. As we’re using array and records, we’re safe. See:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">TeamRoster</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> TeamId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> CurrentContracts <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">TeamRoster</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">)</span> <span class="token punctuation">{</span> TeamId <span class="token operator">=</span> teamId<span class="token punctuation">;</span> CurrentContracts <span class="token operator">=</span> currentContracts<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">TradePlayers</span><span class="token punctuation">(</span><span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGive <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGive</span><span class="token punctuation">(</span>TeamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>playersToGive<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>CurrentContracts<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>p<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Offer cannot include players that are not in the team roster"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGet <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGet</span><span class="token punctuation">(</span>TeamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newRoster <span class="token operator">=</span> CurrentContracts <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>e <span class="token operator">=></span> <span class="token operator">!</span>playersToGive<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>playersToGet<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> CurrentContracts <span class="token operator">=</span> newRoster<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>If you’re afraid of totally removing the connection between aggregate and check, you can also add such a <a href="https://en.wikipedia.org/wiki/Proxy_pattern">proxy</a>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">TradeCheckProxy</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">TradePlayers</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">TeamRoster</span> teamRoster<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">,</span> <span class="token class-name">ICheckTrade</span> tradeChecker<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> checkResult <span class="token operator">=</span> tradeChecker<span class="token punctuation">.</span><span class="token function">CanTradeTo</span><span class="token punctuation">(</span>teamRoster<span class="token punctuation">.</span>TeamId<span class="token punctuation">,</span> teamRoster<span class="token punctuation">.</span>CurrentContracts<span class="token punctuation">,</span> tradeOffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>checkResult <span class="token operator">!=</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span>checkResult<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> teamRoster<span class="token punctuation">.</span><span class="token function">TradePlayers</span><span class="token punctuation">(</span>tradeOffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">TradePlayersHandler</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IAmYourFavouriteDatabase</span> database<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ICheckTrade</span> tradeChecker<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">TradePlayersHandler</span><span class="token punctuation">(</span><span class="token class-name">IAmYourFavouriteDatabase</span> database<span class="token punctuation">,</span> <span class="token class-name">ICheckTrade</span> tradeChecker<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>tradeChecker <span class="token operator">=</span> tradeChecker<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>database <span class="token operator">=</span> database<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> teamRoster <span class="token operator">=</span> <span class="token keyword">await</span> database<span class="token punctuation">.</span><span class="token function">GetById</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> teamRoster<span class="token punctuation">.</span><span class="token function">TradePlayers</span><span class="token punctuation">(</span>tradeOffer<span class="token punctuation">,</span> tradeChecker<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> database<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>teamId<span class="token punctuation">,</span> teamRoster<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>By that, we’re making things that should be explicit, keeping low coupling and high cohesion.</strong> We’re also making our code more testable as we can:</p> <ul> <li>validate business logic without mocks,</li> <li>see the result of our changes through the exposed read-only state,</li> <li>test strategy with unit tests,</li> <li>check the integration of the trade checker and aggregate with proxy.</li> </ul> <p>Can we call it a day now? Of course not!</p> <h2 id="chain-of-responsibility" style="position:relative;"><a href="#chain-of-responsibility" aria-label="chain of responsibility permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Chain of responsibility</h2> <p><strong>We already did a lot, but if we look back at our trade checker implementation, it already does a lot.</strong> And we just started implementing the rules! Soon, it may become a maintenance nightmare.</p> <p>The trade checker verifies several conditions, taking input data and returning a bool if the check isn’t successful, stopping processing further rules.</p> <p>We could break our <em>TradeChecker</em> class into two smaller.</p> <p>See the player’s veto check</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">PlayerVetoTradeCheck</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ICheckTrade</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> PlayerVetoPreferences<span class="token punctuation">></span></span> playersVetoPreferences<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">PlayerVetoTradeCheck</span><span class="token punctuation">(</span><span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> PlayerVetoPreferences<span class="token punctuation">></span></span> playersVetoPreferences<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>playersVetoPreferences <span class="token operator">=</span> playersVetoPreferences<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ICheckTrade<span class="token punctuation">.</span>Result</span> <span class="token function">CanTradeTo</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> playersWithVetoRules <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGive</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>CanVoteTrade<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">AreAnyPlayerWithVetoRightsThatHaveUnknownPreferences</span><span class="token punctuation">(</span>playersWithVetoRules<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>UnknownPlayerVetoPreferences<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">WillAnyPlayerVetoTrade</span><span class="token punctuation">(</span>playersWithVetoRules<span class="token punctuation">,</span> tradeOffer<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>PlayerWillVetoTrade<span class="token punctuation">;</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">AreAnyPlayerWithVetoRightsThatHaveUnknownPreferences</span><span class="token punctuation">(</span><span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>PlayerContract<span class="token punctuation">></span></span> playersWithVetoRules<span class="token punctuation">)</span> <span class="token operator">=></span> playersWithVetoRules<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>playersVetoPreferences<span class="token punctuation">.</span><span class="token function">ContainsKey</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">WillAnyPlayerVetoTrade</span><span class="token punctuation">(</span><span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>PlayerContract<span class="token punctuation">></span></span> playersWithVetoRules<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">)</span> <span class="token operator">=></span> playersWithVetoRules<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token operator">!</span>playersVetoPreferences<span class="token punctuation">[</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">]</span><span class="token punctuation">.</span>AcceptableTeamsAsTradeDestination <span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>tradeOffer<span class="token punctuation">.</span><span class="token function">TradeDestination</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>PlayerId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And maximum salary cap check:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">MaximumSalaryCapTradeCheck</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ICheckTrade</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">NBATradeParameters</span> nbaTradeParameters<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MaximumSalaryCapTradeCheck</span><span class="token punctuation">(</span><span class="token class-name">NBATradeParameters</span> nbaTradeParameters<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>nbaTradeParameters <span class="token operator">=</span> nbaTradeParameters<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ICheckTrade<span class="token punctuation">.</span>Result</span> <span class="token function">CanTradeTo</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> teamId<span class="token punctuation">,</span> <span class="token class-name">PlayerContract<span class="token punctuation">[</span><span class="token punctuation">]</span></span> currentContracts<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGive <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGive</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> playersToGet <span class="token operator">=</span> tradeOffer<span class="token punctuation">.</span><span class="token function">PlayersToGet</span><span class="token punctuation">(</span>teamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newRoster <span class="token operator">=</span> currentContracts <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>e <span class="token operator">=></span> <span class="token operator">!</span>playersToGive<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>playersToGet<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>newRoster<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>c <span class="token operator">=></span> c<span class="token punctuation">.</span>Amount<span class="token punctuation">)</span> <span class="token operator">></span> nbaTradeParameters<span class="token punctuation">.</span>MaxSalaryCap<span class="token punctuation">)</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>PlayerWillVetoTrade<span class="token punctuation">;</span> <span class="token keyword">return</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>The classes are smaller, easier to grok, and more focused.</strong> Now, they only inject the needed parameters, which can be changed separately. We removed the next impedance mismatch.</p> <p>That’s sweet, but we also need to make one more change. Update our proxy to take a list of checks instead of a single one.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">TradeCheckProxy</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">TradePlayers</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">TeamRoster</span> teamRoster<span class="token punctuation">,</span> <span class="token class-name">TradeOffer</span> tradeOffer<span class="token punctuation">,</span> <span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>ICheckTrade<span class="token punctuation">></span></span> tradeCheckers <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> tradeChecker <span class="token keyword">in</span> tradeCheckers<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> checkResult <span class="token operator">=</span> tradeChecker<span class="token punctuation">.</span><span class="token function">CanTradeTo</span><span class="token punctuation">(</span>teamRoster<span class="token punctuation">.</span>TeamId<span class="token punctuation">,</span> teamRoster<span class="token punctuation">.</span>CurrentContracts<span class="token punctuation">,</span> tradeOffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>checkResult <span class="token operator">!=</span> ICheckTrade<span class="token punctuation">.</span>Result<span class="token punctuation">.</span>Ok<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span>checkResult<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> teamRoster<span class="token punctuation">.</span><span class="token function">TradePlayers</span><span class="token punctuation">(</span>tradeOffer<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Foreach is a simple implementation of <a href="https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern">Chain-of-responsibility pattern</a>. We call all checks until the first fail and call business logic only if all succeed.</p> <p>Are we good now? Can we call it a day now? Yes, we finally can.</p> <p><strong>Is the final version the best solution? Of course, it depends.</strong></p> <p>Each step I presented in the article can be your final step. We should start with a simple solution and then evolve.</p> <p><strong>I intended to show you the thought process for designing business logic that’s more complicated than just basic input validation and state updates.</strong></p> <p>I wanted to show how to tackle scenarios, when you need to base your decision on potentially stale data, and how to approach that, finding the right consistency tradeoff.</p> <p>I hope I also showed you the impedance mismatch and how to deal with it using a few simple patterns to keep business logic focused, keeping it cohesive instead of coupled.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Check also an article <a href="https://jeremydmiller.com/2023/10/05/the-lowly-strategy-pattern-is-still-useful/">The Lowly Strategy Pattern is Still Useful</a> from Jeremy D. Miller. Accidentally we decided to write at the same time on this topic. But that’s for good, as those are different perspectives and examples. The more, the merrier!</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Oops I did it again, or how to update past data in Event Sourcing]]>https://event-driven.io/en/how_to_update_past_data_in_event_sourcing/https://event-driven.io/en/how_to_update_past_data_in_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f51d37090439a8cf91fe878273e8a93a/a331c/2023-09-22-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4iAAAuIgGq4t2SAAADJElEQVQozwEZA+b8AHRqdUc8Qx0FBkshFbB2VZyCcjBDVD1MWTxKVjxJVjxJVUBIUj1CSEE+QUk9PUc5N0c3NEcxLUguKEMrJQCegYpxT1FoJxSLRCaVVziHdWY5UF9JUVtKUl1OVWBLTlhNSFFVSU1XREJjSUFpSz90T0B8UkBySTViPS4Aw5WaeSUilTMZt00hnlU0hVBESTlERFReUVRgXlBbgFdTp3Zjlm1je1ZNflVJkmRRrXRavH9hnWZNeUs1AOTY2bVZUak5HadJJpRELpcsJ2k7RF9ca5JzdKyMf9W+oeC1jNuqhNCrlsSVhsuYhtm/ttzFu9KsmbSCZwDl7s7bqX3Ud0i3WTWoIhisMCe8c1zGnIjgzqro58Tl7dPfzqndv6Tj3r/m5c3m4cPk7+Hk+Pjk387jvpwAz5tf38l62beJ2J942pBh0YFVyIJY0pJv25Z62ZR72Jt82qR04Kh43ppu1I1jzYdW0Z1y1rSl2rCUwIlrAHoZD75ZG928XNzKhde6h8isgMSdbcF1Sr9NI8NTI8plLMZRKK85HYkUCmgIA3IlF7NlQLl9XKBsUY1aQABuFA67RyLRaiHbjjLgqlPTl0vLbiyxRRp/GxF2HxJkFw5VCwxVCQ9FEhVAIyFEMy5nQjN4Ry5oQjBjOScAOgoIq2AmmT0hdhMOah4SlikUeyMQOAsGFwABDAEAEQAAKBMWMSktNSgsLicoLCUlMykpOSonOychOSIdABMJDy4QFT8VEjQXFUIZFkMPDQgAAgoAAAwBAAYBASYAADsVGSMmLSMhJyIhIyMfICMfHyIdHiMbGiEXGAAABQ4LChIgDhInFxkiFhoBAgYLAQEIAAEJAAEFAAErAAAvDhEZISYYHiMaHiIcHB8bGhwbGBgYFRcUERQAAAACAwQJCQwUDRMfAgcODQQGCQABBQAAAQAACgEBKwABIAkLFh0jFB0iFRwhFRogFBccFBUYExIVDA4SAAIBAxEOESAcHh0YGgUDBRcKCwIAAAAAAAAAABIBASMAARkFBxQbIRIaHxIYHRIWGxEUGQ8SFgsPEgkND/RK9nPd54JBAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/f51d37090439a8cf91fe878273e8a93a/a331c/2023-09-22-cover.png" srcset="/static/f51d37090439a8cf91fe878273e8a93a/36ca5/2023-09-22-cover.png 200w, /static/f51d37090439a8cf91fe878273e8a93a/a3397/2023-09-22-cover.png 400w, /static/f51d37090439a8cf91fe878273e8a93a/a331c/2023-09-22-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>One team win is a loss for the other. For some people, money loss is a gain for others.</p> <p><strong>In the same way, the challenging parts of Event Sourcing are also its superpowers.</strong> Perspective changes everything.</p> <p><strong>Let’s say that we’re building a hotel housekeeping management system.</strong> Each morning, we prepare a list of the rooms for maids to clean up. The process is more complex than it may seem. We do not only need to take into account guest checkouts; there are also other factors. For instance, we need to make a room full clean up after checkout and every 2 or 3 days of the stay. On other days, we do a quick cleanup, e.g., replace used towels, empty trash bins, etc. We also need to consider maids’ availability, distribute the workload, and ensure they won’t run from one floor to another but have the assignment collocated.</p> <p>Hospitality is not the easiest industry. It’s all about working with people, and we humans are unpredictable. You can see all the complaints you had or heard about from your past. Some of them were justified, some were not. It’s a must to have a clear log of what maid cleaned up and what not.</p> <p><strong>As you can imagine, the initial morning schedule during the day may change.</strong> Some may decide to prolong the stay, some may ask not to do cleanup, some maids can get ill, and we must redistribute the load. The common thing for maids is to have the schedule printed in the morning and go by it. Some chains are trying to modernise that process, embrace that things may change, and be flexible.</p> <p><strong>Let’s say we’re building such a system, giving maid tablets with information on their schedule.</strong> We’re using Event Sourcing to get a precise log of what has happened. We have a stream dedicated to a maid’s daily cleanup schedule. We also have dedicated streams representing the room availability and guest stay.</p> <p>In the cleanup schedule, we have events like:</p> <ul> <li>Schedule Defined,</li> <li>Shift Started,</li> <li>Room Full Cleanup Started,</li> <li>Room Full Cleanup Completed,</li> <li>Shift Finished,</li> <li>etc.</li> </ul> <p><strong>The schedule is defined each night based on the expected state of the hotel the next day. What will happen if the guest prolongs their state and we don’t need to do a full cleanup?</strong> Of course, the schedule will need to be adjusted. That also allows us to optimise it, adding other room for the maid.</p> <p>The same can happen if one maid has an accident, and we must redistribute their tasks (all or the left ones if that happened in the middle of the shift).</p> <p>We may also need more capacity to do the full cleanup. Then, at least, we should note that down to move the clean up for the next day or apologise to guests.</p> <p><strong>That may sound like a lot, especially if we’re responsible for implementing those processes.</strong> If we were doing systems traditionally, this may look easier, as we’d need to override all the data in multiple tables, getting immediate consistency with transactions. That may sound more complex if we’re in the denormalised event-sourced world. And indeed, I’m getting questions about such scenarios quite a lot.</p> <p><strong>Still, overriding the state is not a great way to solve this case.</strong> The fact that we’ve updated data doesn’t mean that person will be notified about that, plus we may lose the precise information about the reasoning behind that change.</p> <p>Events give us more options. We can subscribe to the notification about the guest stays and, based on that information, decide on adjusting the maid schedule that had the particular room assigned for cleanup. What’s more, we can also store the specific information about the reasoning for that in the maid’s daily schedule stream.</p> <p><strong>Depending on our requirements, we can store the following events:</strong></p> <ul> <li><strong>Schedule Updated</strong> - containing either the complete new schedule. It’s not perfect, but it can be fine if, more likely, we’ll be changing the majority of the schedule. That may happen if we, e.g. want to put a new assignment in the middle of the schedule. Read more in <a href="https://event-driven.io/en/state-obsession/">Anti-patterns in event modelling - State Obsession</a>.</li> <li><strong>Schedule Invalidated</strong> - for the case from above, it may be better to accept that it’s better to create a new schedule instead of trying to update it for a big reshuffle of the schedule. We can put a <em>tombstone</em> event marking that we’re invalidating the old schedule and setting up a new one. It can also be useful in the case of a maid accident. We can put the reason there or create another dedicated event type depending on our needs.</li> <li><strong>Room Assignment Removed</strong> + <strong>Room Assignment Added</strong> - we can add more than one event for each specific operation. The challenge here is knowing the correlation and reasoning for those events so that they both happened because of the same trigger.</li> <li><strong>Room Stay Extended</strong> + <strong>Schedule Adjusted</strong> - we can store the <em>raw</em> event we got externally that triggered rescheduling and the event showing how we adjusted the schedule. That gives us good traceability, and keeping all the information gives us a complete tracing of the process.</li> <li><strong>Schedule Adjusted</strong> - we can, of course, just keep the single event with a reason and the link to the original event (e.g. as causation ID in metadata, read more in <a href="/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/">Set up OpenTelemetry with Event Sourcing and Marten</a>). The downside is that by looking at another stream, we don’t have the full reasoning.</li> </ul> <p><strong>Nevertheless, whatever option we choose, we give a complete view and understanding as to why we retrofit some change.</strong> We don’t have that typically in the regular state updates.</p> <p>We can model such mishaps explicitly as workflows and subscribe for the particular events running compensating operations. I explained that in detail in the <a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed">webinar about implementing distributed processes</a>:</p> <p><a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB8klEQVQoz4WST2sTURTF5wO59du49QO4kSBVQdwJbsRWwbiK1Z24UBQL8R8aCCQpaWtCkjYTJmmczLy/M4kxTX/yJk1sCsXF4XJ595173jnPG40ipFTEsVggEkgpkXJZJSIaoZRCa4PWOqtKacIwwhhLZfeAexv3+fD+G54xBgfrYC3GLvqLWBA5qFVVSjI/nbNdKPB08zG53AbecqtTIOIYLRXqrM/gLkuJ0RprE6xNscZikglCWWazGc/yeX62aty5ewsvikQ2HOmE6n6N+OAd0phse0bonj2eUu60qVWKNHc/U23u0f3+ClHf4c8cnufzFN/skLtxE8/5tpCvEUIgYx+pdearI7XOgu4hYbfDcdhnOOgy+DVgFDSJB0ecnM7ZerTF5tWXXLtyHc8RLTxSmCQl6vsMK2+JghZKGWpBiH1wm5PCE/RkitFnXidjtElIkoR2q037q0+5WMFzqWaESmVDUa/B8McLouYntJJ88UP62w8ZV0vIJM3m1sPRpGnKlN+EcfgvlJXCXpvj0mtCp7JTp+qH+JWPaHs+6XUoqbKg/K6P5+QvD4xNEIOAqFFCHJaRfp39fsxwr4QRMdpcTuqe3uv1nML1A+P8SSfZt9DJmNSFkk7OferLCYMgwHPJmoubM58WUKte/5ew0WjyFxc6K8nB1bJ+AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2023-09-22-webinar.png" srcset="/static/aeaac4b117eefd05e7a7793b42fef48d/36ca5/2023-09-22-webinar.png 200w, /static/aeaac4b117eefd05e7a7793b42fef48d/a3397/2023-09-22-webinar.png 400w, /static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2023-09-22-webinar.png 800w, /static/aeaac4b117eefd05e7a7793b42fef48d/e4699/2023-09-22-webinar.png 957w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p><strong>We can do the same for the compensating operation if we see some bug in our system.</strong> If we’re doing a banking system and we see that some clients had wrong account balance calculations, we can refund all of them. We can even define the migration process for event streams; we can have a one-time code that will append those events to the streams, triggering the other flows and needed notifications without doing <em>cowboy database updates</em>.</p> <p>There’s also another layer to it.</p> <p><strong>People fear the wrong state in the event streams while not being afraid of traditional storage.</strong> And here’s the deal: both may be broken the same way. The difference is that we may not even notice that it’s broken in the traditional storage. We’ll see that in Event Sourcing because we have tools for that.</p> <p><strong>I believe that such observability and detecting discrepancies and fixing them (semi)automated way is a big win.</strong> We’re not only able to do it but also leave self-explanatory traces as to why we did it this way. By that, we can even correct the wrong correction.</p> <p>So you tell me: is fixing the data scenarios a pros or cons of the event-sourced systems?</p> <p>Read also more in:</p> <ul> <li><a href="/en/what_texting_ex_has_to_do_with_event_driven_design/">What texting your Ex has to do with Event-Driven Design?</a>,</li> <li><a href="/en/simple_events_versioning_patterns/">Simple patterns for events schema versioning</a>,</li> <li><a href="/en/event_versioning_with_marten/">Event Versioning with Marten</a>,</li> <li><a href="/en/saga_process_manager_distributed_transactions/">Saga and Process Manager - distributed processes in practice</a>,</li> <li><a href="/en/event_driven_distributed_processes_by_example/">Event-driven distributed processes by example</a>,</li> <li><a href="/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/">Set up OpenTelemetry with Event Sourcing and Marten</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Architecture Weekly became the Substack Bestseller!]]>https://event-driven.io/en/architecture_weekly_became_substack_bestseller/https://event-driven.io/en/architecture_weekly_became_substack_bestseller/<p><strong>Boom, I got this week such a nice picture from the Substack. My <a href="https://www.architecture-weekly.com/">Architecture Weekly</a> newsletter appears to have become the Substack Bestseller for getting 100 paid subscribers.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2973566b573d2cda22cffd3f5be4da25/639e1/2023-09-17-cover-2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAADWUlEQVQ4y5WUWU8jRxSF+9/nLS+Jkuc8oSiKhomwZDyOMoPHxjYGvID3Ha+Axwt2V3Xbdb+oqoGMIjRRSjqqrfvUqXvvKc90Cr60c5j8OzHlJKaWxnSvOUzbmOEtppXFDMrRfHAT7Q8qmO4lppHBVFNi6mkjyynB3ow9aWWR+A9iEj9jjr9DinGop5FWDhnVkLtbpFtA+kVk3ED6JcTu94vQLUAzA7cfoX8tav0Fz1SSYmLfY85/FXP6I/LpFxiWkUYaqX9GOpfIoIIMSsigjBnXI9JWDtPOMy38KdOLD/KQOcFvXxvPhBqTPsJkf8MkfkJyv0M7jzQy2FDQOkcs7FrnAuleOmLpXbm1fTPPvnXhvte1DJ4BxN9g7InzLrJdRj+VPiClJHL7EamnIsU2PO1cRN67dt/hDrmCTh7dKuCJsZT/auYAywkMb8CqmXdhVHUhoJ6K0MxEpFapJewVUe0rvP1+z/bpiTDQ7MOAQGtEhOBg8HXATmkE0EGIqCe23Rv23SKmkY3UW9WdC+iXUL0K3m63I5FIcHp6yvHxMfF4nGQySSwWI5fNcnR0ROrsjNjJCfFEgvcnMd69/4PV8gvsVsh9HxlWXNzVoIpnjGG73bJardhsNg4vY6UU6/XajW2/Xq/w/R2r5ZKDMU65wyGEfhHdLeEFQcBoNOLu7s71k8mEfr/PcDh8nY/HYzefTiYO43GE+WzG4fCcg2EZbWMYhiHz+dzh4eGBx8fHV9i1xWLh1l+xWHA/n3EIAxBBQo0s7qCdQ7cv8WwC/lfbPIB1z3Oh2/jZTNPKoVuXUQwt6Tdh46SekFYeqfyFNLNRdm1tWk/3ii4punkREdr2ptKvCRcjqH6Kitr63KJ7hfSKzp7UUv9N+KLQjQM/KuLbM6RhrVhA6plnpKH+GTWsffvKXx/kCGufkWoqIrYPh3uVziMH2Rhu1y4pL9LkLYW8XHnSRMpJpFOIiGpnkced19Nii1yHe/EOh71R2w1hGIq1oS0j278qt2TmgBlVMfZNnHUi3A+Qadv6XET7ToxWynhBoGhVr8XW3Ww2c/Vni9n3/X+kqieXRevXNy7yeiGttXVKOLbGD8PQWHUvsA7SStlroO7v0Dcp9KTt5lr50V4E0VoZS6aUyv4NuTPZ563np1AAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/2973566b573d2cda22cffd3f5be4da25/a331c/2023-09-17-cover-2.png" srcset="/static/2973566b573d2cda22cffd3f5be4da25/36ca5/2023-09-17-cover-2.png 200w, /static/2973566b573d2cda22cffd3f5be4da25/a3397/2023-09-17-cover-2.png 400w, /static/2973566b573d2cda22cffd3f5be4da25/a331c/2023-09-17-cover-2.png 800w, /static/2973566b573d2cda22cffd3f5be4da25/639e1/2023-09-17-cover-2.png 932w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>It’s a nice checkpoint, so let’s look back at how it started! You need to cherish good moments.</p> <p><strong>Being hungry for knowledge is a double-edged sword.</strong> One can say that it’s impossible to know too much, but I think that in a specific context, that’s possible.</p> <p>My father often told me that I had a flash in the pan. I think that it wasn’t a precise judgement. I wasn’t quickly giving up, but I had too many hobbies and jumped from one to another, and it was easy to be interested in something new. Or just distracted.</p> <p><strong>That’s precisely what was also happening with my technical musings.</strong> I think I did projects in most of the popular platforms of paradigms, mobile, backend, frontend, legacy, serverless, business intelligence, .NET, Java, Node.js, management, product design, etc. I was definitely not T-shaped but rather underscored.</p> <p><strong>As I was interested in all that stuff, trying to build a holistic view of our industry to deliver better projects, I was trying to catch up with most of the news.</strong> Like most of us, I started with a classical developer browser view: hundreds of open tabs. Of course, occasionally, I had them cleaned by random browser crashes. And all the links poof disappeared in the kingdom of doom.</p> <p>Too often, I knew that I had a great link there, but I didn’t remember the author or title and couldn’t find it again. I was trying to devise different ideas for managing that: bookmarks, dedicated GitHub repos, YouTube playlists, and tools like Notion. All of those tools had their benefits, but the knowledge was dispersed, and they were still a mental barrier to keep them up to date. Kinda like a squirrel that hides nutts for later and forgets where it was.</p> <p><strong>Luckily, I had a new idea that aged better!</strong></p> <p>I’m a simple man, so the simple methods work best for me. Inspired by the immutability of the Architecture Decision Record and Event Sourcing, I just created an append-only log of valuable resources (articles, videos, whitepapers, etc.). I started to group them by week and general categories like Architecture, DevOps, Databases, Management, etc. and technologies. Firstly, I used it just for myself. Now, I could search in the markdown file by author, title, and keywords or skim approximately by time, remembering how long ago I found a particular link. Not perfect, not terrible. Good enough.</p> <p>I put it in the GitHub repository that’s still a basis for each release: <a href="https://github.com/oskardudycz/ArchitectureWeekly">https://github.com/oskardudycz/ArchitectureWeekly</a>.</p> <p>I shared that with my friends. As I used the GitHub release function, it appeared that some subscribed for the new releases using built-in GitHub notifications. Some started to ask me if I could <em>“setup a proper newsletter”</em>. I was postponing that idea for some time, as I didn’t want to put yet another burden on myself.</p> <p><strong>Yet, finally, I decided to use Substack, as it was the most straightforward tool.</strong> It allowed me to share those links both through the page and send email notifications about new releases. It was also easy to use, looked professional and gave me more options.</p> <p><strong>What options? At some point <a href="https://github.com/sponsors/oskardudycz">I set up GitHub Sponsors</a> with the idea to make my Open Source <em>“work”</em> more sustainable.</strong> I wanted to give them something more as a gratitude for their support. Of course, not gonna lie, also as a hook to attract more of them. That idea was to do monthly webinars. I decided, why not have both and build the community for the people who want something more in their career?</p> <p>Some of them already were asking me for recommendations, comments and help. A lot of questions repeated. Some of the answers were a basis for the <a href="https://event-driven.io">articles on my blog</a>, but I wanted to have it more interactive.</p> <p>I’ve set up a Discord channel.</p> <p><strong>That’s how the final idea has matured: Let’s build an exclusive community that:</strong></p> <ul> <li>share the knowledge among themselves,</li> <li>can support each other and ask questions,</li> <li>tackles ideas, as one of the issues of technical leaders is solitude. Not being able to find others to discuss and challenge their thoughts.</li> </ul> <p><strong>Fast forward to the time of writing, Architecture Weekly has:</strong></p> <ul> <li>3644 free subscribers,</li> <li>103 paid subscribers,</li> <li>19 GitHub sponsors,</li> <li><a href="https://go.particular.net/oskardudycz">Particular as a sponsor</a>.</li> </ul> <p><strong>We had so far <a href="https://www.architecture-weekly.com/p/webinars">13 webinars</a> run not only by me but also by authorities:</strong></p> <ol> <li><a href="https://www.architecture-weekly.com/p/webinar-1-from-crud-to-event-sourcing">From CRUD to Event Sourcing</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short">Keep your streams short! Or how to model Event-Sourced systems efficiently</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed">Implementing Distributed Processes</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice">From CRUD to CQRS in Practice</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-5-architecture-weekly-100">Architecture Weekly 100 Edition - Live Q&#x26;A</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-6-webinar-with-alexey-zimarev">Alexey Zimarev - You don’t need an Event Sourcing framework. Or do you?</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven">Design and test Event-Driven projections and read models</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates">Slim down your aggregates!</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-9-radek-maziarka-modularization">Radek Maziarka - Modularization with Event Storming Process Level</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers">PostgreSQL Superpowers in Practice</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-11-maciej-mj-jedrzejewski">Maciej “MJ” Jędrzejewski - Evolutionary Architecture: The What. The Why. The How.</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-12-jeremy-d-miller-simplify">Jeremy D. Miller: Simplify your architecture with Wolverine</a></li> <li><a href="https://www.architecture-weekly.com/p/webinar-13-yves-goeleven-the-fantastic">Yves Goeleven - The Fantastic 9</a></li> </ol> <p>That’s in total: 21 Hours, 13 Minutes, 42 seconds!</p> <p>As you see, it’s not a one-man show anymore.</p> <p><strong>It may be a bold statement, but I think the subscription price is a bargain, compared to the amount of knowledge you can find in those videos.</strong> Better than a lot of online courses.</p> <p><strong>Is it, for me, a significant source of income right now?</strong> Not really. To be true, I’m using the paywall as a filter for the interested people to build an engaged community here. It’s still, for me, more of a way to connect and get mutual feedback with subscribers rather than a real source of income. Still, much appreciated, and I’m thankful to all.</p> <p><strong>What are the next plans?</strong></p> <ul> <li>Continue to do that until it is helpful for me and my subscribers.</li> <li>I want to get more guests for webinars to make the community more diverse and supplement parts I’m not experienced enough.</li> <li>I’m thinking about building the LLM bot that’d answer based on the knowledge in the selected articles.</li> <li>I’m also considering workshops purely for the community.</li> </ul> <p><strong>If you have ideas, don’t be shy to share them!.</strong></p> <p><strong><a href="https://www.architecture-weekly.com/">Feel invited to join our community!</a></strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. This article <a href="https://www.architecture-weekly.com/p/architecture-weekly-became-the-substack">was also posted on the Architecture Weekly</a>.</p> <p>p.s.2 <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[My journey from Aggregates to Functional Composition]]>https://event-driven.io/en/my_journey_from_aggregates/https://event-driven.io/en/my_journey_from_aggregates/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/9e34b03aadbd1e43e0a675a76b5a3bec/a331c/2023-09-08-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADiElEQVQ4yz2R20+bBQDFv35taaFAP0pbbvUrk5Ze2CjQCpRSygqlV2hpodwKiGVM3UKCyMVthDAWzBQvM1uixjkxbiNi9jA1MzExzlejzr25LPNZ3/wLfiaY+HROcvI75+EIHeEQ7QP9dMYGCWcTBDNJ/KkYvZkU3SMjdKfT9GYz9OUyx9qfHyOQHqYrlcCXTOLPDBMYidOVjOKLRRC8kTA9iQiZZDexPhez81EiUzkGp8ZZWl1gef1Fxs/OEZ6ZITaZo72vh9BYlmA2w+x0nGR6kN5c5v8RoTMeZSjRRz7kYiPuYSXZypnVIm9sTjM64MTvNrH/ZpHFtVexNVmwViixOuzML+X4YX+Bry7k8Ye66Z8YPy4VetMpvL4WFmNennyyxu5ogL2dAoNdVophD+8VQkzFfVzaWqLTVsu95SRzQTcTGT8PduZ5dLVIIdxGIJfj9FgWoX9sFJ/XzcHFSf4+usz+whB7W6PYTKX8erDFw8svMRPtYHN9lr2FCH8dbPL+RBdt9iquLaV4/NYiw95GOhNxBgoFBE8wwAmbzO3Nce5v59k5P8JMphuPRc+jzy5w59woE0Mn2bk4TaHfxc9X51jos9PhqGagVea73UmWQna6W50kp/IITS0tCIKA321hr3iary9NsJ3rYTLo4vu9Il32BsI9z7O9lqUY7+DPu6vYaiuQ9BpOWY38cus1ntxd435hgB/fnkdodLpQKEUiXisP9l/h2b13uLU4SFtqGPO5XQRvjOoaMzqdFk9THcuTYRSiAr3RSGlFJS/HPDz7YoWfpmM8vbOMUF1Tg0pTgq9R4rebr/PPtx/x5bSfek8HQuIsKn8GfU0DOr2EZDQi2+3ILjdmayOacj0nzWXcHvJyaHHwzWIUodJgoFRbQqzDysMPzvPHx+usBB1o9QZULj+iVEu5Xo/OVEudLBMcDNEe8NNqt6I1GPHpKviw3sZhg5N3PS0IOklCW6JmcaCZp0dXeHxtmfQpCypdJWrZjUIUUZZoaa6T8Lc1Y7Y8h6lKIuVzojWa8OsNfCo3cyg72TvhRCiv1CMqROqrNNw4M8Tv11dx1leh06gRSsoQRRGFSs1ojwuf04IgiFQYzZToJAS1hqhUzeeygyPZxXW5GaFMV45apTx+OvWCg5sbhWOvVIooBAGFQoFCVKLRG9BK1ag12uNcpfyPyUvV3LDY2KiX8ZlM/AubVs82sP2otgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/9e34b03aadbd1e43e0a675a76b5a3bec/a331c/2023-09-08-cover.png" srcset="/static/9e34b03aadbd1e43e0a675a76b5a3bec/36ca5/2023-09-08-cover.png 200w, /static/9e34b03aadbd1e43e0a675a76b5a3bec/a3397/2023-09-08-cover.png 400w, /static/9e34b03aadbd1e43e0a675a76b5a3bec/a331c/2023-09-08-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I was recently asked what drove me to mostly use immutable data and methods rather than the Aggregate Pattern in my samples and videos.</strong> I decided that it might be worth sharing my answer here to give you more context for my journey. Especially that <a href="/en/what_does_a_construction_failure_have_to_do_with_our_authorities/">I wrote once why is that important</a>.</p> <p>It’s the stage of evolution that I’m currently at.</p> <p><strong>I started with classical aggregates following Domain Driven Design with a typical Object-Oriented tactical pattern.</strong> So, encapsulate data and behaviour in a single class. Then, allow changes only through public methods and expose data only in read-only mode.</p> <p><strong>Then I noticed that mentally, I was struggling.</strong> Such a way pushed me to think more about the data than the business logic. It might be just me, but it’s harder to reason what this aggregate actually does with the mixture of data and behaviour. Through discussion with others, I also noticed that I’m not the only one.</p> <p>I see the value of encapsulation in the Object-Oriented way, but classes are not the only way to achieve that.</p> <p><strong>Of course, one may say that <em>“you just didn’t understand the pattern”</em> and one may be right.</strong> Yet, if the pattern is too sophisticated and too many people struggle to apply it correctly, there’s something wrong with it. Don’t hate the player; hate the game.</p> <p>Of course, I don’t hate aggregate; there might be multiple reasons why people fail on that: lack of practical resources, not enough katas, wrong explanations or common patterns that shouldn’t be considered best practices.</p> <p>See more in the great (and short!) talk by Thomas Ploch:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/zlFqjD2LKlE?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>Whatever the real reason, I started to look at how I could do modelling better and reflect it in the code. So, the hunting for simplicity has begun.</strong></p> <p>At the same time, I was spending a lot of time doing and learning Event Sourcing. That made an evident impact. I slowly realised that the classically Object-Oriented style, especially C# and Java style, does not match what we have in Event Sourcing streams.</p> <p>You can think about event streams as the story. In a good story, the main character transitions; Frodo didn’t start as a hero, aye? That’s also what’s happening with the entities in Event Sourcing. They’re usually state machines. Each state is actually a different aggregate. It has a similar set of data but different and the same with behaviours.</p> <p>Some example? Sure, if you think about the room reservation on Booking.com, you could model it as a single aggregate, but actually, there’s a distinct set of operations you can do on the initiated, confirmed and completed reservation. You also have different data. The same can be noticed in others like the shopping cart, order, etc.</p> <p><strong>The unified, classical C#/Java version became increasingly obsolete to me.</strong></p> <p>I also worked a lot in recent years in TypeScript, which gave me another perspective on coding with its algebraic types and was definitely inspired by works like <a href="https://pragprog.com/titles/swdddf/domain-modeling-made-functional/">Domain-Modelling Made Functional</a> by Scott Wlaschin, <a href="https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider">Decider</a> by Jérémie Chassaing. I also had numerous discussions with Ruben Bartelink, where he patiently tried to drive me into doing functional way correctly.</p> <p><strong>I also spend a lot of time explaining Event Sourcing in various ways and seeing how others are doing so.</strong> I noticed that most explanations started with DDD and the Aggregate pattern. I noticed that this confuses people and suggests that you must understand a lot before even trying to touch Event Sourcing.</p> <p>Frankly, most of our models are rather simple, especially if we cut the read model fluff from them. We rarely touch really complex business domains. Yet, we quite often make them complicated.</p> <p>I was thinking about how to show others that Event Sourcing, in a nutshell, is a simple pattern, and you might not need all this complexity. I started to explain it with: <em>“Here are your events, your data and here are functions that run business logic.”</em></p> <p><strong>I noticed data+functions, for most people, was much more straightforward to grok.</strong></p> <p>That triggered the reinforcement loop, and I again started to think, <em>“hey, if that’s working for here, then why wouldn’t it work as the default option?”</em></p> <p><strong>Some say Entity+Functions creates the <a href="https://martinfowler.com/bliki/AnemicDomainModel.html">Anaemic Domain Model</a>, but I disagree.</strong> I don’t think that code structures make the model anaemic. I don’t think that using this or that syntax makes it such. What makes it anaemic is if we don’t reflect the business model and behaviour in it.</p> <p>From my perspective, entity+functions can be much richer than many Object-Oriented implementations I saw. In the end, it’s just syntax. The same thing can be achieved in multiple ways.</p> <p>I think that if people are too focused on encapsulation then that’s also a smell of some organisational issue. If we don’t believe we can catch those changes in review, tests or collaboration, then there’s a bigger issue to solve first.</p> <p>Still, if you’re using patterns like Decider, you achieve encapsulation, but by composition. Still, neither cleanness nor functional way is the end goal for me.</p> <p><strong>I don’t care much about that being functional or not.</strong> What I like is to get is good composition. Having structures helps me with that. I think that composition of the small blocks is the only way to tame complexity and cognitive load.</p> <p>That’s why I rarely call my code functional, as some purists would say that’s not pure. And I don’t care about purity; I care about correctness, enabling composition and reducing cognitive load.</p> <p><strong>Last but not least, using immutable structures makes it easier to trust my code.</strong> I know that they won’t be modified, and if the object was created, then it was already validated, and I don’t need to repeat IFs/Validations, etc.</p> <p>Thus, again, code is simpler and easier to understand.</p> <p>At least for me.</p> <p>But I also noticed that’s also a common path for people working more with Event Sourcing.</p> <p>I’m not saying which is better or worse in general; I’m always trying to show multiple ways of achieving that in my <a href="/en/training/">workshops</a>, so both DDD and more functional ways, as different people have different perspectives and preferences.</p> <p>Still, that’s my current state of the art and reasoning about it.</p> <p>Let’s see how different it will be after the next few years.</p> <p>Read also more about my journey in:</p> <ul> <li><a href="/en/how_to_effectively_compose_your_business_logic/">How to effectively compose your business logic</a></li> <li><a href="/en/slim_your_entities_with_event_sourcing/">Slim your aggregates with Event Sourcing!</a></li> <li><a href="/en/type_script_node_Js_event_sourcing/">Straightforward Event Sourcing with TypeScript and NodeJS</a></li> <li><a href="/en/vertical_slices_in_practice/">Vertical Slices in practice</a></li> <li><a href="/en/onion_clean_code/">What onion has to do with Clean Code?</a></li> <li><a href="/en/explicit_validation_in_csharp_just_got_simpler/">Explicit validation in C# just got simpler!</a></li> <li><a href="/en/removability_over_maintainability/">Removability over Maintainability</a></li> <li><a href="/en/prototype_underestimated_design_skill/">Prototyping, an underestimated design skill</a></li> </ul> <p>Check also webinar where I show transition step by step.</p> <p><a href="https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABl0lEQVQoz43RWU6DQADG8SmNIgrVJm5xiRVKKda61K0WgbDMDAyIZdiM9cElHsHEd72AJ/EmnsloE22qD07+LzPJ7+HLAEVpN5u7SqNdl7YkUZVEVa636vWWJKripip+vYhisyFvK8rOdqvTkNskPH99eXp+egQuosMcGDuw78C+FxRh/5pEVzgooJfaiNqIOjhxcYK81MVJdHEJbc80EIAwHs11Yz+8pPltnN4k+V1a3If9a0xyiFMXJcNsSB2cQZz+wjAOogHN7+LsFkcDFA3I+RUiheulDk7GGscQUc3wT7rW7qHu96xIs9T2yV5HM6zwt//ZPJyNvKwmqgLPAwDeMu/9IQNfp3OoI1IMx//g0YuNKCL5ek0ReIGbEWxpg8g1UGIAAPv/w8XahjzDcUvzC5N8pcROcRzHMMy/MA6K1bVNlmWr1So7OVEul5kyA0pg/+gv7OLk+w8cSLGfS3JrYXFpeXmlMjvHCxVBmJ2e5g+OzTEM/Qyc9mB3pJ6GLSvSdaKf+aYZmGZgGJ/pRtDV0GinZ/gDu8efv/k2/20AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/4548b02bcf56a39ea74f712e3c29152d/a331c/2022-08-31-webinar.png" srcset="/static/4548b02bcf56a39ea74f712e3c29152d/36ca5/2022-08-31-webinar.png 200w, /static/4548b02bcf56a39ea74f712e3c29152d/a3397/2022-08-31-webinar.png 400w, /static/4548b02bcf56a39ea74f712e3c29152d/a331c/2022-08-31-webinar.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Event transformations, a tool to keep our processes loosely coupled]]>https://event-driven.io/en/event_transformations_and_loosely_coupling/https://event-driven.io/en/event_transformations_and_loosely_coupling/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f959d882f2258f0782415fcceda6e6c9/a331c/2023-08-31-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAACmElEQVQ4y3WTX0jTURTHhYqgHuotCHoR9mDQQwb1EEQh2kTzT9bM+Wc9VJs6TZ04TcE0LbMoMsz8P821pW7+SfszUbGUkqIGWdJ6Sh/UUtvMQmu/fWLq5s+sC5d7OZzzOfee8z0+AC6Xy30gsH4JTiceH88Wx/xt83EJyxhbexs6hZIXJjOjb0fo0BlYnLN7oa6VBGLAP4Hui2N6ltqkVMrzL9KtPEN9kJSyRDUdGg2fBwY9kbiTu+FLCQRhFSyCLgGFxQVSC/WozhVRFiDl8kkZ+pwLWB92MfHuPbMOB78WFtbXYyWJ+KU+wgo5OrGSrAID2VfuoQmJ4VaiknatluHWFt6M2pZ8FiYmseoaGH/0mPmxsdUyCKvV9wLNXS/JKLqPIiSFCsluKjWZ1KSc5050DPX5BQxps6kLlPLAV0JNcAitwSE8T09n8sOoF7rcFNH/q5uHKD16AmNQABkbNnPXbw91Oj0V/ntp3LSR66Hh6A8eIruqlo42M7p4BQNmM7hh4qa4L07n8rMNSiVnpcGUqpKwqNMwtjVToFJxUxpK45ZtNO3YSbX/fm5HRFGtTKLzUjG/f/wUyeYvfU1/+khJURHRCQkkaTSU11SQGydHt8sX49bt1B05zLNUBcZrN2jQNWGSy7F/mfo/cMg6hkyhRSLxIzY2mRpDP7kyNdp9B0gOi8QQE8eIRk2PwcjTnj6Gr5YwOzW5FigIy7Cprw4Co/KJkKVxKlZLkDSesMgUjsu1qLJyyMzLo7CwmG5TJxbLE3p7TQz29TP3fX6tsL2jJ7iw27/hcjmxWq3EyGWEh0dQVamjo72TY6GhKFUqLL19tLSaUJxWMPz61UqssBYoHiePw8zMLOPj416bzWZboznx+Hl0+AfOj1aPWD6U3gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/f959d882f2258f0782415fcceda6e6c9/a331c/2023-08-31-cover.png" srcset="/static/f959d882f2258f0782415fcceda6e6c9/36ca5/2023-08-31-cover.png 200w, /static/f959d882f2258f0782415fcceda6e6c9/a3397/2023-08-31-cover.png 400w, /static/f959d882f2258f0782415fcceda6e6c9/a331c/2023-08-31-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>One of the biggest pains in traditional software design is accidental complexity.</strong> We want to understand and reflect on the business process in the code, but our perspective becomes immediately blurred. We add a new field, change the business flow a bit and w realise that <em>“dammit, foreign keys won’t work now”</em> or <em>“now views will be broken”</em>. Blood pressure increases, and instead of continuing to focus on the business logic, we’re starting to do it all at once.</p> <p>That’s never a pleasant experience.</p> <p><strong>Event Sourcing can help with that, as it does things differently.</strong> First, we’re focusing on the business process. We distil the critical points as events, e.g. Order Confirmed, Invoice Issued, etc. They should be recorded as the results of business operations. That creates a simple pattern that allows us to <a href="/en/prototype_underestimated_design_skill/">prototype and quickly verify our understanding</a>.</p> <p><strong>It’s easier to keep backward compatibility when adding or updating events.</strong> With <a href="/en/simple_events_versioning_patterns/">simple versioning patterns</a>, we could even deploy changes to business logic and then consider how to reflect that in read models. That means we’re getting a decent separation of concerns between our business logic and read models.</p> <p>That sounds too good to be true, and indeed, we need to do a tradeoff analysis. I explained that in:</p> <ul> <li><a href="/en/events_should_be_as_small_as_possible/">Events should be as small as possible, right?</a></li> <li><a href="/en/on_putting_stream_id_in_event_data/">Stream ids, event types prefixes and other event data you might not want to slice off</a></li> <li><a href="/en/projections_and_event_metadata/">Using event metadata in event-driven projections</a></li> <li><a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">How to create projections of events for nested object structures?</a></li> <li><a href="/en/i_will_just_add_one_more_field/">Anti-patterns in event modelling - I’ll just add one more field</a>.</li> <li><a href="/en/projections_and_read_models_in_event_driven_architecture/">Guide to Projections and Read Models in Event-Driven Architecture</a></li> </ul> <p>Event Sourcing won’t remove the need for a proper design exercise, but it can help streamline this effort, reducing the cognitive load.</p> <p><strong>We’ll discuss today another scenario: how to keep events granular and not coupled to read model needs.</strong></p> <p>Let’s say we’re building a feature for managing the work schedule. We should enable defining the employee allocation for the set of days in the selected period. The event reflecting that could look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Allocation</span><span class="token punctuation">(</span> <span class="token class-name">DateTime</span> Day<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">double</span></span> Hours <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">EmployeeAllocated</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> EmployeeId<span class="token punctuation">,</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>Allocation<span class="token punctuation">></span></span> Allocations <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Of course, each employee can have multiple allocations so that the employee allocation stream will contain a sequence of those events. Data from further events can also override allocation.</p> <p><strong>A lot of us work on monthly schedules, but not all.</strong> Some of us can have weekly, 10 days, or other type of schedules. Our software should be flexible and allow defining all of them.</p> <p><strong>Still, we can have different perspectives on the same data.</strong> Accounting is usually working in the monthly schedules. We might want to view allocation in such a way:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MonthlyAllocation</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> EmployeeId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateOnly</span> Month <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">double</span></span> Hours <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Yet, if we’re using a weekly schedule, the week can start in one month and finish in the other. So, one <em>EmployeeAllocated</em> event can update multiple read models.</p> <p>Monthly allocation id could be a string in the format: <em>{EmployeeId}|{Month:yyyy-MM-dd}</em>, as the employee id and the first day of the month shapes the unique value.</p> <p><strong>We could update <em>MonthlyAllocation</em> read model based on the <em>EmployeeAllocated</em> event as follows:</strong></p> <ol> <li>Take the employee id and all allocated dates.</li> <li>Take the first date of the month from each date with the employee id and select the <em>MonthlyAllocation</em> read model.</li> <li>Update selected <em>MonthlyAllocation</em> with data from the event.</li> </ol> <p>Sounds simple, right? But it’s not if we want to make it performant.</p> <p>Let’s look at the following payload of the <em>EmployeeAllocated</em> event.</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"EmployeId"</span><span class="token operator">:</span> <span class="token string">"90033055-c5a3-4e98-aaf8-1ca14554c346"</span><span class="token punctuation">,</span> <span class="token property">"Allocations"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token property">"Day"</span><span class="token operator">:</span> <span class="token string">"2023-08-30"</span><span class="token punctuation">,</span> <span class="token property">"Hours"</span><span class="token operator">:</span> <span class="token string">"3"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"Day"</span><span class="token operator">:</span> <span class="token string">"2023-08-31"</span><span class="token punctuation">,</span> <span class="token property">"Hours"</span><span class="token operator">:</span> <span class="token string">"2"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"Day"</span><span class="token operator">:</span> <span class="token string">"2023-09-01"</span><span class="token punctuation">,</span> <span class="token property">"Hours"</span><span class="token operator">:</span> <span class="token string">"8"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"Day"</span><span class="token operator">:</span> <span class="token string">"2023-09-02"</span><span class="token punctuation">,</span> <span class="token property">"Hours"</span><span class="token operator">:</span> <span class="token string">"6"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token property">"Day"</span><span class="token operator">:</span> <span class="token string">"2023-09-03"</span><span class="token punctuation">,</span> <span class="token property">"Hours"</span><span class="token operator">:</span> <span class="token string">"4"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span></code></pre></div> <p>We have here an allocation that’s touching two months: August and September. Each of them will have multiple days. For the naive implementation, we could iterate through allocations and update read models as described above. That could work on a smaller scale, but not in the long term. For our example, instead of doing two updates (one for August, the other for September), we’d do five for each allocation. Consider having more events like that and monthly allocations for hundreds of employees. Yeah… We need to do better than that.</p> <p><strong>We could come up with the idea that it’d be better to have an event reflecting employee’s monthly allocation definition.</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">EmployeeAllocatedInMonth</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> EmployeeId<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> Month<span class="token punctuation">,</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>Allocation<span class="token punctuation">></span></span> Allocations <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That sounds like a decent move, but it’s not, as this is different from how the business works. It works as described before, so defining flexible allocations. If we try to replace the <em>EmployeeAllocated</em> event with that one, we’d be cheating. We’d bend the business process to how we’d like to view our data.</p> <p><strong>That’s a no-go.</strong></p> <p>Yet, what if we transformed our <em>EmployeeAllocated</em> into <em>EmployeeAllocatedInMonth</em>, keeping the original flow and events intact? That’s a much better idea.</p> <p><strong>One way of doing it is to transform them and store results durable in another stream.</strong> Then, we update our read model based on the events from transformed data. That’s how <a href="https://developers.eventstore.com/server/v23.6/projections.html#introduction">EventStoreDB projections works</a>.</p> <p>That’s a valid solution. The downside is that it increases the size of the database, which may not be ideal if we just want to update the read model and won’t need those events for other cases.</p> <p><strong>The other option is to perform transformations in the memory before running the projection update.</strong></p> <p>Let’s say that we prefer this option and use Marten. We might want to have our projection look as follows (read more in <a href="/en/projections_in_marten_explained/">introduction to Marten projections</a>):</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MonthlyAllocationProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">MultiStreamProjection<span class="token punctuation">&lt;</span>MonthlyAllocation<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">MonthlyAllocation</span> allocation<span class="token punctuation">,</span> <span class="token class-name">EmployeeAllocatedInMonth</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> allocation<span class="token punctuation">.</span>EmployeeId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>EmployeeId<span class="token punctuation">;</span> allocation<span class="token punctuation">.</span>Month <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Month<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> hours <span class="token operator">=</span> @<span class="token keyword">event</span> <span class="token punctuation">.</span>Allocations <span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>Hours<span class="token punctuation">)</span><span class="token punctuation">;</span> allocation<span class="token punctuation">.</span>Hours <span class="token operator">+=</span> hours<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>That looks simple, as it should. Now, how to do the transformation? Marten provides a feature called <em><a href="https://martendb.io/events/projections/multi-stream-projections.html#view-projection-with-custom-grouper">IAggregateGrouper</a></em>. The name may sound enigmatic, but it enables custom transformation and grouping of events before we apply projection logic. So, all the slicing and dicing we need.</p> <p>Let’s define the custom grouper, then. I’ll show you the whole code and then explain what we’re doing step by step.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MonthlyAllocationGrouper</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IAggregateGrouper<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">Group</span><span class="token punctuation">(</span> <span class="token class-name">IQuerySession</span> session<span class="token punctuation">,</span> <span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>IEvent<span class="token punctuation">></span></span> events<span class="token punctuation">,</span> <span class="token class-name">ITenantSliceGroup<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> grouping <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> allocations <span class="token operator">=</span> events <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">OfType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEvent<span class="token punctuation">&lt;</span>EmployeeAllocated<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> monthlyAllocations <span class="token operator">=</span> allocations <span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token operator">=></span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>Allocations<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span> allocation <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>EmployeeId<span class="token punctuation">,</span> Allocation <span class="token operator">=</span> allocation<span class="token punctuation">,</span> Month <span class="token operator">=</span> allocation<span class="token punctuation">.</span>Day<span class="token punctuation">.</span><span class="token function">ToStartOfMonth</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Source <span class="token operator">=</span> @<span class="token keyword">event</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">GroupBy</span><span class="token punctuation">(</span>allocation <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> allocation<span class="token punctuation">.</span>EmployeeId<span class="token punctuation">,</span> allocation<span class="token punctuation">.</span>Month<span class="token punctuation">,</span> allocation<span class="token punctuation">.</span>Source <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>monthlyAllocation <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> Key <span class="token operator">=</span> <span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>EmployeeId</span><span class="token punctuation">}</span></span><span class="token string">|</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>Month</span><span class="token format-string"><span class="token punctuation">:</span>yyyy-MM-dd</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> Event <span class="token operator">=</span> monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>Source<span class="token punctuation">.</span><span class="token function">WithData</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EmployeeAllocatedInMonth</span><span class="token punctuation">(</span> monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>EmployeeId<span class="token punctuation">,</span> monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>Month<span class="token punctuation">,</span> monthlyAllocation<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>a <span class="token operator">=></span> a<span class="token punctuation">.</span>Allocation<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> monthlyAllocation <span class="token keyword">in</span> monthlyAllocations<span class="token punctuation">)</span> <span class="token punctuation">{</span> grouping<span class="token punctuation">.</span><span class="token function">AddEvents</span><span class="token punctuation">(</span> monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">,</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> monthlyAllocation<span class="token punctuation">.</span>Event <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> Task<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We recommend running such projections as asynchronous ones. Marten has a background process called <a href="https://martendb.io/events/projections/async-daemon.html">AsyncDaemon</a> that’s doing many optimisations like grouping and parallelisation. It tries to group events by projection type, tenants, etc. Such grouping we call slice. Each slice will try to load read models and apply changes to all of them at once. Also, if the piece has multiple updates to the same read model instance, it’ll only be updated once. Read more on <a href="/en/scaling_out_marten/#scaling-async-projections">Scaling async projections</a>.</p> <p>Getting back to our grouper. The first thing we do is to select the events we’d like to handle in our projection. We’re selecting the original <em>EmployeeAllocated</em> events.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> allocations <span class="token operator">=</span> events <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">OfType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEvent<span class="token punctuation">&lt;</span>EmployeeAllocated<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then, we need to transform them into <em>EmployeeAllocatedInMonth</em>. We’re starting by flattening the allocations list to get each as a separate row. We’re also transforming the date into the first day of the month:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">allocations <span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token operator">=></span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>Allocations<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span> allocation <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>EmployeeId<span class="token punctuation">,</span> Allocation <span class="token operator">=</span> allocation<span class="token punctuation">,</span> Month <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DateOnly</span><span class="token punctuation">(</span>allocation<span class="token punctuation">.</span>Day<span class="token punctuation">.</span>Year<span class="token punctuation">,</span> allocation<span class="token punctuation">.</span>Day<span class="token punctuation">.</span>Month<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Source <span class="token operator">=</span> @<span class="token keyword">event</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span></code></pre></div> <p>Then we’re grouping them by employee id and month:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> monthlyAllocations <span class="token operator">=</span> allocations <span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token operator">=></span> <span class="token comment">/* (...) flattening allocations */</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">GroupBy</span><span class="token punctuation">(</span>allocation <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> allocation<span class="token punctuation">.</span>EmployeeId<span class="token punctuation">,</span> allocation<span class="token punctuation">.</span>Month<span class="token punctuation">,</span> allocation<span class="token punctuation">.</span>Source <span class="token punctuation">}</span> <span class="token punctuation">)</span></code></pre></div> <p>And getting new events based on the data from grouping:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> monthlyAllocations <span class="token operator">=</span> allocations <span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token operator">=></span> <span class="token comment">/* (...) flattening allocations */</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">GroupBy</span><span class="token punctuation">(</span>allocation <span class="token operator">=></span> <span class="token comment">/* (...) grouping by employee id and month */</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>monthlyAllocation <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> Key <span class="token operator">=</span> <span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>EmployeeId</span><span class="token punctuation">}</span></span><span class="token string">|</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>Month</span><span class="token format-string"><span class="token punctuation">:</span>yyyy-MM-dd</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> Event <span class="token operator">=</span> monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>Source<span class="token punctuation">.</span><span class="token function">WithData</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EmployeeAllocatedInMonth</span><span class="token punctuation">(</span> monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>EmployeeId<span class="token punctuation">,</span> monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">.</span>Month<span class="token punctuation">,</span> monthlyAllocation<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>a <span class="token operator">=></span> a<span class="token punctuation">.</span>Allocation<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re generating:</p> <ul> <li>Key - it’ll be used to select read model instances.</li> <li>Event - this event will be handled by projection. We need to create the event with metadata. By using the <em>WithData</em> method, new events will have the same metadata as the original event but different data.</li> </ul> <p>We must also tell Marten to use those transformed data in the custom grouping.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> monthlyAllocation <span class="token keyword">in</span> monthlyAllocations<span class="token punctuation">)</span> <span class="token punctuation">{</span> grouping<span class="token punctuation">.</span><span class="token function">AddEvents</span><span class="token punctuation">(</span> monthlyAllocation<span class="token punctuation">.</span>Key<span class="token punctuation">,</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> monthlyAllocation<span class="token punctuation">.</span>Event <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As the final step, we also need to tell Projection that we’re transforming <em>EmployeeAllocated</em> events using custom grouper. We do that by registering them in a projection constructor:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MonthlyAllocationProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">MultiStreamProjection<span class="token punctuation">&lt;</span>MonthlyAllocation<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">MonthlyAllocationProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">CustomGrouping</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">MonthlyAllocationGrouper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">TransformsEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>EmployeeAllocated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">MonthlyAllocation</span> allocation<span class="token punctuation">,</span> <span class="token class-name">EmployeeAllocatedInMonth</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> allocation<span class="token punctuation">.</span>EmployeeId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>EmployeeId<span class="token punctuation">;</span> allocation<span class="token punctuation">.</span>Month <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Month<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> hours <span class="token operator">=</span> @<span class="token keyword">event</span> <span class="token punctuation">.</span>Allocations <span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>Hours<span class="token punctuation">)</span><span class="token punctuation">;</span> allocation<span class="token punctuation">.</span>Hours <span class="token operator">+=</span> hours<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>And that’s it. See the <a href="https://github.com/JasperFx/marten/blob/4dafe48f4209a90b5c66ee8a9a2a60e15d7ca991/src/EventSourcingTests/Projections/MultiStreamProjections/CustomGroupers/custom_grouper_with_events_transformation.cs">full sample</a>.</p> <p>I understand that it may sound a bit complex. Still, it’s a powerful and flexible mechanism that allows us to achieve our main goal: keep our business workflow loosely coupled with read models.</p> <p><strong>No matter which event store implementation you’re using, it’s worth focusing on keeping our business workflow reflecting the real world.</strong> We can make tradeoffs, but we should also consider other options to avoid rotten compromises. Event transformations can help achieve that.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Prototyping, an underestimated design skill]]>https://event-driven.io/en/prototype_underestimated_design_skill/https://event-driven.io/en/prototype_underestimated_design_skill/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/74f82846a3ca42f3d9a57c9e19c3cd9a/a331c/2023-08-25-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4jAAAuIwF4pT92AAACVElEQVQoz33Qy08TQQDH8f1bjAboE2uhtjxsSFFBwYRSqKXdbrvdziytpbbFg2Asy87ssjN0twWNGA8SEz2a+EhITNCoF+NFkRhjo/FiOHgxejEmWtMKDRDj5zK/w3wz2WXwLgghQjRCqUYW6wchhJTI4qJG67t+Ae/BNJcsy7quKwpG80UkzaqyrC4saGqRyjJV1AVKKktL+D9xpVIe8fOn/WfTmRNDUV/MP5bNBCNu94jL4xnwIQURQhBC/4gRQrpuFC4IcGo4FIsPCbCnr7/bO2rv7LG225yOIxhjbV+s7MAYl41yKMsFJsa8vcPDx05F/EfPDB52OhwdTqfL5VIUhRKKMW4mzPyOubk53TB6u3rNZkubqbXV1HKoxdTSarFaLGaz2WazSpKkqqokSc2EAQ0QQp7ni8UiECcD48GJcIRlo/EYx3HRMBsNR1gBQIwx2ItJNgAAOI4jlCqK8uJO6fOD8uVLs/lcLpufXruhfV9fuW6QcuVKUhCSuzDNBUGSlpdvzvC1h8Wvq1MiO+b19vmOn3yswdq9i5srGYOW8oVpnuf3x4lEIpVOv33z+ufTa7Un2rdXa9WPn6rV9++qH7aqm7+eX/29rn7ZeKZoepRlIYR7YhECfyB49/ZqbePW+nIhnTmfy0/n8nWpbKE0k/nxSNt6eV9InovHYgCAXXFjOxyeTne/QQmEMDQRZiPboiw7HgoTRY7zqQMH20dHg6IoCo2PZwRBEEXo8w22mTos9q5u74A4KUJY//9/AQBTk2JgPNxmc1vtHpvdzXHbj/8BpKlhu+dUdFkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/74f82846a3ca42f3d9a57c9e19c3cd9a/a331c/2023-08-25-cover.png" srcset="/static/74f82846a3ca42f3d9a57c9e19c3cd9a/36ca5/2023-08-25-cover.png 200w, /static/74f82846a3ca42f3d9a57c9e19c3cd9a/a3397/2023-08-25-cover.png 400w, /static/74f82846a3ca42f3d9a57c9e19c3cd9a/a331c/2023-08-25-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Coding is an underestimated part of the design.</strong> When we think about the design, we immediately fall to whiteboard diagrams, sticky notes, or endless discussions. That’s fine and one way of doing things, but we should do more - prototyping and working with code during the design phase. I mentioned last time that we <a href="/en/behaviour_driven_design_is_not_about_tests/">don’t need to fall into Test-Driven Development or Behaviour-Driven Design immediately</a>. I showed before my approach to <a href="/en/how_to_effectively_compose_your_business_logic/">composing business logic</a>; today, I’d like to expand on that.</p> <p><strong>We’ll use the following pieces in our recipe:</strong></p> <ul> <li><a href="https://www.youtube.com/watch?v=Up7LcbGZFuo">Type-Driven Design</a></li> <li><a href="https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider">Decider pattern</a></li> <li>Event Sourcing</li> <li>and code that with C# and <a href="https://martendb.io/">Marten</a>.</li> </ul> <p><strong>Why such ingredients?</strong></p> <p>Type-Driven Design focuses on shaping our types in a way that doesn’t allow unexpected scenarios to happen. <em>“Talk is cheap; show me the code.”</em> Sure, let’s say that we initially modelled our class as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartStatus</span> Status<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span></span> ProductItems<span class="token punctuation">,</span> <span class="token class-name">DateTime<span class="token punctuation">?</span></span> ConfirmedAt <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token class-name">DateTime<span class="token punctuation">?</span></span> CanceledAt <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span><span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsClosed <span class="token operator">=></span> ShoppingCartStatus<span class="token punctuation">.</span>Closed<span class="token punctuation">.</span><span class="token function">HasFlag</span><span class="token punctuation">(</span>Status<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p>That’s quite fine, but it allows us to create <em>ShoppingCart</em> with a cancelled date and pending status. Nothing stops us from creating instances with wrong values besides good will.</p> <p>In our logic, we also need to constantly check all the fields, for instance, if the status is pending while adding a product item.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">static</span> <span class="token return-type class-name">ProductItemAddedToShoppingCart</span> <span class="token function">Handle</span><span class="token punctuation">(</span> <span class="token class-name">AddProductItemToShoppingCart</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> shoppingCart <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> pricedProductItem<span class="token punctuation">)</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>IsClosed<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Adding product item for cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">shoppingCart<span class="token punctuation">.</span>Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> cartId<span class="token punctuation">,</span> pricedProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s okay to copy and paste it around the methods for simple logic, as we have here. But still, it’s easy to forget that, especially when our logic evolves.</p> <p><strong>How could Type-Driven Design help us in that?</strong></p> <p>Let’s say that we have the following business rules:</p> <ul> <li>Add product items to the pending shopping cart,</li> <li>Remove product items from the pending shopping cart if they were added before.</li> <li>Confirm pending shopping cart if it has products,</li> <li>Cancel pending shopping cart.</li> </ul> <p>So effectively, our shopping cart is a state machine that can be:</p> <ul> <li>Empty - initial state,</li> <li>Pending - it was opened,</li> <li>Closed - either confirmed or cancelled. We can merge that into this state, as we don’t have business logic differentiating those two states.</li> </ul> <p>Let’s show that in code:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Empty</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCart</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Pending</span><span class="token punctuation">(</span> <span class="token punctuation">(</span><span class="token class-name">ProductId</span> ProductId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Quantity<span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span> ProductItems <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCart</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">HasEnough</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token operator">=></span> ProductItems <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> productItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token operator">>=</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> HasItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> ProductItems<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Closed</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCart</span></span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token comment">// Not to allow inheritance</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// To make Marten happy</span> <span class="token punctuation">}</span></code></pre></div> <p>The syntax is a bit weird, and <a href="/en/union_types_in_csharp/">I blame C# for that</a>. Yet, effectively, it does what we want - so, saying explicitly that a shopping cart can be one of those states.</p> <p>Interestingly, we also get more expressive types, as we can have a different set of data and possible methods for each state.</p> <p>At first glance, the code may look a bit oversimplified. I removed dates and flattened <em>ProductItem</em> into a tuple. Yet, I just left what we need to run our business logic. For it, we need to know the state of the shopping cart and the product items quantity. Other data is required just for the read models, and we’re now modelling only business logic. (Read more about that process in <a href="/en/slim_your_entities_with_event_sourcing/">Slim your aggregates with Event Sourcing!</a>).</p> <p><strong>We achieved a code that’s expressive and simple enough to demonstrate our requirements around consistency.</strong></p> <p><strong>Now, let’s define the behaviour we must handle for our shopping cart.</strong> We can again use types for that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartCommand</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Open</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">ClientId</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartCommand</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AddProductItem</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartCommand</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RemoveProductItem</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartCommand</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Confirm</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartCommand</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Cancel</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartCommand</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Those are all commands with the data. As you see, I’m modelling time explicitly here. We can inject current time to command, making it easier to text and our logic more predictable and self-contained. Similarly, I assume that the product price will be on the application layer, and in command, we already have all the information about the product.</p> <p>Let’s now code the business logic:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Opened</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">Open</span> command<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Opened</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItemAdded</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">AddProductItem</span> command<span class="token punctuation">,</span> <span class="token class-name">Pending</span> shoppingCart<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAdded</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>ProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItemRemoved</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">RemoveProductItem</span> command<span class="token punctuation">,</span> <span class="token class-name">Pending</span> shoppingCart<span class="token punctuation">)</span> <span class="token operator">=></span> shoppingCart<span class="token punctuation">.</span><span class="token function">HasEnough</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>ProductItem<span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemRemoved</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>ProductItem<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Not enough product items to remove."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Confirmed</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">Confirm</span> command<span class="token punctuation">,</span> <span class="token class-name">Pending</span> shoppingCart<span class="token punctuation">)</span> <span class="token operator">=></span> shoppingCart<span class="token punctuation">.</span>HasItems <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Confirmed</span><span class="token punctuation">(</span>DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Shopping cart is empty!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Canceled</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">Cancel</span> command<span class="token punctuation">,</span> <span class="token class-name">Pending</span> shoppingCart<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Canceled</span><span class="token punctuation">(</span>DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">Pending</span> <span class="token function">EnsureIsPending</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">)</span> <span class="token operator">=></span> shoppingCart <span class="token keyword">as</span> <span class="token class-name">Pending <span class="token punctuation">?</span></span><span class="token punctuation">?</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Invalid operation for '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">shoppingCart<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name</span><span class="token punctuation">}</span></span><span class="token string">' shopping card."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The business logic looks simple, and that’s the goal! That’s only possible because of the explicit types. Thanks to them, we removed a lot of redundant checks (e.g. around the status, etc.). We also use simple functions that take commands and current state returning events.</p> <p>Thanks to that, we keep all the data in events and don’t need to repeat it in the domain model. That’s the power of Event Sourcing. <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">Our current state is always ephemeral and built in memory from events</a>. Thus, as long we have all the information in events, we can shape our domain model to our needs. <a href="/en/how_events_can_help_on_making_state_based_approach_efficient/">This pattern also works for regular state</a>.</p> <p>Let’s express now how to build the state from events by extending Shopping Cart class:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartEvent</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> @<span class="token keyword">event</span> <span class="token keyword">switch</span> <span class="token punctuation">{</span> Opened <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Pending</span><span class="token punctuation">(</span>Array<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Empty</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token punctuation">(</span>ProductId ProductId<span class="token punctuation">,</span> <span class="token keyword">int</span> Quantity<span class="token punctuation">)</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ProductItemAdded <span class="token punctuation">(</span><span class="token keyword">var</span> <span class="token punctuation">(</span>productId<span class="token punctuation">,</span> quantity<span class="token punctuation">,</span> _<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">is</span> <span class="token class-name">Pending</span> pending <span class="token punctuation">?</span> pending <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> pending<span class="token punctuation">.</span>ProductItems <span class="token punctuation">.</span><span class="token function">Concat</span><span class="token punctuation">(</span><span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token punctuation">(</span>productId<span class="token punctuation">,</span> quantity<span class="token punctuation">.</span>Value<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">,</span> ProductItemRemoved <span class="token punctuation">(</span><span class="token keyword">var</span> <span class="token punctuation">(</span>productId<span class="token punctuation">,</span> quantity<span class="token punctuation">,</span> _<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">is</span> <span class="token class-name">Pending</span> pending <span class="token punctuation">?</span> pending <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> pending<span class="token punctuation">.</span>ProductItems <span class="token punctuation">.</span><span class="token function">Concat</span><span class="token punctuation">(</span><span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token punctuation">(</span>productId<span class="token punctuation">,</span> <span class="token operator">-</span>quantity<span class="token punctuation">.</span>Value<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">,</span> Confirmed <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">is</span> <span class="token class-name">Pending <span class="token punctuation">?</span></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Closed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">,</span> Canceled <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">is</span> <span class="token class-name">Pending <span class="token punctuation">?</span></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Closed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">this</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Opened</span><span class="token punctuation">(</span><span class="token class-name">ClientId</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> OpenedAt<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> ProductItem<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> ProductItem<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Confirmed</span><span class="token punctuation">(</span><span class="token class-name">DateTimeOffset</span> ConfirmedAt<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Canceled</span><span class="token punctuation">(</span><span class="token class-name">DateTimeOffset</span> CanceledAt<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Such a method also works as the documentation for state transition. Isn’t that neat?</p> <p>It is, but still, when we get our shopping cart from events and want to run the business logic on it, we need to ensure that it’s in the expected state.</p> <p><strong>To do it, let’s define a general <em>Decide</em> method that takes command and state and makes a decision respecting our defined types.</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartEvent</span> <span class="token function">Decide</span><span class="token punctuation">(</span> <span class="token class-name">ShoppingCartCommand</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> state <span class="token punctuation">)</span> <span class="token operator">=></span> command <span class="token keyword">switch</span> <span class="token punctuation">{</span> <span class="token return-type class-name">Open</span> open <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>open<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">AddProductItem</span> addProduct <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>addProduct<span class="token punctuation">,</span> state<span class="token punctuation">.</span><span class="token function">EnsureIsPending</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">RemoveProductItem</span> removeProduct <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>removeProduct<span class="token punctuation">,</span> state<span class="token punctuation">.</span><span class="token function">EnsureIsPending</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">Confirm</span> confirm <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>confirm<span class="token punctuation">,</span> state<span class="token punctuation">.</span><span class="token function">EnsureIsPending</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">Cancel</span> cancel <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>cancel<span class="token punctuation">,</span> state<span class="token punctuation">.</span><span class="token function">EnsureIsPending</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Cannot handle </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">command<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name</span><span class="token punctuation">}</span></span><span class="token string"> command"</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">Pending</span> <span class="token function">EnsureIsPending</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">)</span> <span class="token operator">=></span> shoppingCart <span class="token keyword">as</span> <span class="token class-name">Pending <span class="token punctuation">?</span></span><span class="token punctuation">?</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Invalid operation for '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">shoppingCart<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name</span><span class="token punctuation">}</span></span><span class="token string">' shopping card."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p>Now, if we’re using Marten, then we can generalise our decision processing to:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DocumentSessionExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">Decide</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEntity<span class="token punctuation">,</span> TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEntity<span class="token punctuation">,</span> TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">></span></span> decide<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TEntity<span class="token punctuation">></span></span> getDefault<span class="token punctuation">,</span> <span class="token class-name">Guid</span> streamId<span class="token punctuation">,</span> <span class="token class-name">TCommand</span> command<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token operator">=</span> <span class="token keyword">default</span> <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TEntity</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token operator">=></span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">WriteToAggregate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEntity<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>streamId<span class="token punctuation">,</span> stream <span class="token operator">=></span> stream<span class="token punctuation">.</span><span class="token function">AppendMany</span><span class="token punctuation">(</span><span class="token function">decide</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> stream<span class="token punctuation">.</span>Aggregate <span class="token operator">??</span> <span class="token function">getDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Cast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re using the <a href="https://martendb.io/scenarios/command_handler_workflow.html#writetoaggregate">WriteToAggregate</a> method that loads the current state from events using defined earlier <em>Apply</em> method. It allows encapsulating command handling logic.</p> <p>We can use it to define processing for Shopping Cart:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartDocumentSessionExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token function">Decide</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartId</span> streamId<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartCommand</span> command<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token operator">=</span> <span class="token keyword">default</span> <span class="token punctuation">)</span> <span class="token operator">=></span> session<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Decide</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCart<span class="token punctuation">,</span> ShoppingCartCommand<span class="token punctuation">,</span> ShoppingCartEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token punctuation">(</span>c<span class="token punctuation">,</span> s<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> ShoppingCartService<span class="token punctuation">.</span><span class="token function">Decide</span><span class="token punctuation">(</span>c<span class="token punctuation">,</span> s<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> streamId<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> command<span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We can use it then in our application code (e.g. controller method or endpoint):</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">Decide</span><span class="token punctuation">(</span> shoppingCartId<span class="token punctuation">,</span> command<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">.</span>None <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Thanks to the Type-Driven Design, we’re getting expressiveness and simplicity. From Event Sourcing, we’re sprinkling it with a focus on business. Decider helps to compose that, and Marten makes it real.</p> <p>We’re <a href="/en/stacking_the_bricks/">stacking the bricks</a> and gradually building our code from smaller, composable building blocks. That lets us focus on the design and get faster feedback loops thanks to faster prototyping with real software.</p> <p>See the full code in my <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Workshops/IntroductionToEventSourcing/Solved/08-BusinessLogic.Marten/Immutable/Solution3">sample repo</a>.</p> <p>I also showed how you can do it similarly in:</p> <ul> <li><a href="/en/how_to_effectively_compose_your_business_logic/">Java</a></li> <li><a href="/en/type_script_node_Js_event_sourcing/">TypeScript</a></li> <li><a href="/en/writing_and_testing_business_logic_in_fsharp/">F#</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Of course, I do not intend to tell you that you should drop regular design exercises and don’t do regular design. Prototyping is a supplement that can spice up your thinking and help to verify your hypothesis quicker. Read more on my design process in <a href="/en/how_to_design_software_architecture_pragmatically">How to design software architecture pragmatically</a>.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Behaviour-Driven Design is more than tests]]>https://event-driven.io/en/behaviour_driven_design_is_not_about_tests/https://event-driven.io/en/behaviour_driven_design_is_not_about_tests/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0aaeb7f0054d926d5e20bfdd55bfa0b0/a331c/2023-08-17-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAAC4jAAAuIwF4pT92AAADp0lEQVQ4yyXO209bBRwH8P4TPvjkZkxMkDg1YVvECRuZjpIDk0ILa4Fya2mh93a9wjm9nJ5eDz0tLT1AWwp0dAFp1DmcIS7qdDG+bA9bzLZo4ouLMyb+Af19zfQf+OSjSLIu8CEb5eJ+4jwGyFKMKnkekuBDJuJFTD8EQfMBRLsON6sFFKNeVCUe9XIaSc5FYswL/XAPCcFFqqwJUIR9i1h2G/8DV2NubObjFDIPQgwwcE50wTHWBaemAzMDpxEyDiIxzyAwcxXl1SjSEQ/dqEiI+kztDGulDSkGRdAxC7d5gpKskzIRD7byUfp824IjeRKC7U2IoQEcyUOo8OfQLBlRE/WIuEeRE0LIxrx0uFfGZj7WTgYMVC1E/wenNUrirpuIdc1DFln67iiOWzU3eFMHyuwlVLkOFP2dqGWm8eCbFHYLdqzGgxB5H9XlDPLJUDvt0VFDFqCwG8YxremnFdc8CUEzcskAidwcdkQ7pJAWxqE34FC/hbCFQdyrRpHXgXWqkOScKGU5amzlkAq7qSJYaX8zDcWE6jJGlRco4jWTGLWjtceRsDyOpck+FFMeuI0fw6HtwckNHo2kFRErg72yA9XVRaylgtSoSC/hdiVhp3opAYWG6cWVC2fItaClgmDBs/txengSgBTowd0DH1p1O8LMaXw20oltcxe+lqfx4ukOfj6OYEMMUW09g1KWbVfTbqoUeCj06n5cG+olvYYhzqPD80dpevytH2uhSzjeMSLLKuF6/xXcmTyDHf3baCZG8eKXDdzddyIbtlO1lEY5u9wu8E6qFhNQGLSDWJodIdaoog1Wi09L81STpuC3XETYfgWe+fNw9L+GlPZdOAc6EZz9CIXIOFJOJebGlSSsOLFbFto5zkKbL4eLU1dh1DF0WFiiB8dhFNkRckydhWTrg0/3DtYTU/jpJA+foRuZlT60anOYYF7Hl40o7uzbqJK7jmoh1k55p0nOsVC4TNdgnxujr5o++utpCScNI/GGV1FbPoUd4T3cv+XHP78f4F7Lih+afahHT6G0chF/PGnh2Y8uOt7nsKBlKOmbIVnioMg6hpG0DFGraKTnj0q4vTVFzfwwKikVErYOPL4Xx9+/beOkuYSjnBr1wCdYj6jw68Nt/PlEpro4B6v6QxI9GirzFigKC90omrpJdvbSNstAMp+l/bgKh+IkcotdOFodwxfyDCTbOeyuDGOPUyO7cB63N034/sBP1agGSePldtmlpLJPjX8BsGiD9sYDnOsAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/0aaeb7f0054d926d5e20bfdd55bfa0b0/a331c/2023-08-17-cover.png" srcset="/static/0aaeb7f0054d926d5e20bfdd55bfa0b0/36ca5/2023-08-17-cover.png 200w, /static/0aaeb7f0054d926d5e20bfdd55bfa0b0/a3397/2023-08-17-cover.png 400w, /static/0aaeb7f0054d926d5e20bfdd55bfa0b0/a331c/2023-08-17-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Why did I name the testing library <em><a href="https://github.com/oskardudycz/Ogooreck">Ogooreck</a></em>?</strong> Between my friends, I’m well known for my lame jokes. <a href="https://github.com/oskardudycz/Ogooreck">Ogooreck</a> is one of them.</p> <p>Ogooreck is a phonetical written Ogórek. Seeing the vowel, you may be already guessing it’s a Polish word, and you’re correct. Ogórek in Polish means Cucumber. The intriguing part is that it’s one of the rare Polish words that succeeded and got into other languages. <a href="https://en.wiktionary.org/wiki/Gurke">German also took the phonetic and named this delicious vegetable Gurke</a>. And the small one is called <a href="https://en.wiktionary.org/wiki/gherkin">gherkin</a>.</p> <p><strong>Still, fondness for the terrible jokes wasn’t the only reason I named Ogooreck like that.</strong> It was also a way to express that I don’t want to build yet another <a href="https://cucumber.io/">Cucumber</a> clone.</p> <p><strong>I like Behaviour-Driven Design. Focusing on the behaviour of our system is one of the foundations of my software design.</strong> That’s also a reason why I’m a big fan of <a href="/en/how_to_effectively_compose_your_business_logic/">modelling by events</a>, <a href="/en/cqrs_facts_and_myths_explained/">CQRS</a>, <a href="/en/vertical_slices_in_practice/">Vertical Slices</a> and <a href="en/how_to_slice_the_codebase_effectively/">Feature Folders</a>. They all help keep an eye on the problem we need to solve.</p> <p>In my world, the behaviour of the system should be reflected in all the places. So design, backend, frontend and tests.</p> <p>Let’s check the main points behind BDD that Dan North wrote when <a href="https://dannorth.net/introducing-bdd/">introducing it in his article</a>:</p> <blockquote> <p>Test method names should be sentences</p> <p>A simple sentence template keeps test methods focused</p> <p>An expressive test name is helpful when a test fails</p> <p>“Behaviour” is a more useful word than “test”</p> <p>JBehave emphasizes behaviour over testing</p> <p>Determine the next most important behaviour</p> <p>Requirements are behaviour, too</p> <p>BDD provides a “ubiquitous language” for analysis</p> <p>Acceptance criteria should be executable</p> </blockquote> <p>Even though you can read between those lines that Behaviour-Driven Design is more than just about testing, the initial context related to tests puts it into a corner. Dan North, years later, gave a talk with a very telling title:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/6nSwRSbc27g?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>Not only tests took over the perspective.</strong> Also, trying to streamline work between the business and developers. Many people hoped we could just talk with business, write behaviours in the form given/when/then and automatically translate that to tests. That’s a noble idea, but…</p> <p><strong>In reality, what was supposed to be a connector between the business, became an excuse.</strong> Developers took it as a chance to outsource the responsibility of the behaviour to domain experts, business analysts and testers. Now developers could continue what they liked the most, typing on the keyboard and doing that their way. <em>“QA will handle that”</em>. What’s worse, too often, those tests became flattened to testing User Interface and not even User Experience.</p> <p>There were (and still are) frameworks trying to build <a href="https://en.wikipedia.org/wiki/Domain-specific_language">Domain Specific Languages</a> and use them in testing. We have an even bigger graveyard of those who tried and failed, as many projects tried to use them in tests.</p> <p><strong>I think that one of the reasons why they fail is that they’re mostly not part of the design and development process.</strong> If we keep them aside and do not have behaviour as ubiquitous in all aspects of the process, they will never be a priority. It will always be easy to postpone them <em>for later</em> a.k.a <em>never</em>. Don’t get me wrong, I can see that work, especially now with <a href="en/chat_gpt_revolution_or_not/">the help of the tools like Large Language Models</a>. Yet it requires consistency and trust.</p> <p><strong>That’s also why Ogooreck is not an ambitious tool but focused.</strong> No Domain Specific Language, separate testing environment, and no delusion that non-dev people will use it.</p> <p>I think that one of the reasons why dev people didn’t use BDD tooling was that they had a lot of ceremonies. They weren’t easy to debug and cluttered. They were also not focused on the stuff devs typically need to test.</p> <p>Is Ogooreck better? For me, yes! I created it because I wanted to be able to write tests in my Open Source projects quickly. As I was making contributions after hours, time was a critical factor. I also wanted to make them more expressive in their structure, with a consistent shape/style that makes it easy to infer the behaviour.</p> <p>Tests for business logic thanks to that can look like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenInitiatedGroupCheckout_WhenRecordGuestCheckoutCompletionTwice_ThenIgnores</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> guestStaysIds <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GroupCheckoutInitiated</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> clerkId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GuestCheckoutsInitiated</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GuestCheckoutCompleted</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> now<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>state <span class="token operator">=></span> state<span class="token punctuation">.</span><span class="token function">RecordGuestCheckoutCompletion</span><span class="token punctuation">(</span>guestStaysIds<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenInitiatedGroupCheckout_WhenRecordLastGuestCheckoutCompletion_ThenCompletes</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> guestStaysIds <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GroupCheckoutInitiated</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> clerkId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GuestCheckoutsInitiated</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GuestCheckoutCompleted</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">[</span><span class="token number">0</span><span class="token punctuation">]</span><span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GuestCheckoutCompleted</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">,</span> now<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>state <span class="token operator">=></span> state<span class="token punctuation">.</span><span class="token function">RecordGuestCheckoutCompletion</span><span class="token punctuation">(</span>guestStaysIds<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GuestCheckoutCompleted</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">[</span><span class="token number">2</span><span class="token punctuation">]</span><span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GroupCheckoutCompleted</span><span class="token punctuation">(</span>groupCheckoutId<span class="token punctuation">,</span> guestStaysIds<span class="token punctuation">,</span> now<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And tests for API integration testing:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">Put_Should_Return_OK_And_Confirm_Shopping_Cart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API <span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span> <span class="token string">"Shopping cart with product item"</span><span class="token punctuation">,</span> <span class="token function">OpenShoppingCart</span><span class="token punctuation">(</span>ClientId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">AddProductItem</span><span class="token punctuation">(</span>ProductItem<span class="token punctuation">,</span> <span class="token named-parameter punctuation">expectedVersion</span><span class="token punctuation">:</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span> <span class="token string">"Confirm shopping cart"</span><span class="token punctuation">,</span> PUT<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span>ctx <span class="token operator">=></span> <span class="token interpolation-string"><span class="token string">$"/api/ShoppingCarts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">ctx<span class="token punctuation">.</span><span class="token function">OpenedShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">/confirmation"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">HEADERS</span><span class="token punctuation">(</span><span class="token function">IF_MATCH</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>OK<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">And</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> <span class="token string">"Get Updated shopping cart details"</span> GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span>ctx <span class="token operator">=></span> <span class="token interpolation-string"><span class="token string">$"/api/ShoppingCarts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">ctx<span class="token punctuation">.</span><span class="token function">OpenedShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Until</span><span class="token punctuation">(</span><span class="token function">RESPONSE_ETAG_IS</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> OK<span class="token punctuation">,</span> <span class="token generic-method"><span class="token function">RESPONSE_BODY</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>details<span class="token punctuation">,</span> ctx<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> details<span class="token punctuation">.</span>Id<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>ctx<span class="token punctuation">.</span><span class="token function">OpenedShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> details<span class="token punctuation">.</span>Status<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">)</span><span class="token punctuation">;</span> details<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span>Count<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> details<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>Carts<span class="token punctuation">.</span>ShoppingCarts<span class="token punctuation">.</span>Products<span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>ProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> ProductItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> details<span class="token punctuation">.</span>ClientId<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>ClientId<span class="token punctuation">)</span><span class="token punctuation">;</span> details<span class="token punctuation">.</span>Version<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">RequestDefinition</span> <span class="token function">OpenShoppingCart</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> clientId <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">SEND</span><span class="token punctuation">(</span> <span class="token string">"Open ShoppingCart"</span><span class="token punctuation">,</span> POST<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/ShoppingCarts"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">OpenShoppingCartRequest</span><span class="token punctuation">(</span>clientId <span class="token operator">??</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">RequestDefinition</span> <span class="token function">AddProductItem</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRequest</span> productItem<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> expectedVersion <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">SEND</span><span class="token punctuation">(</span> <span class="token string">"Add new product"</span><span class="token punctuation">,</span> POST<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span>ctx <span class="token operator">=></span> <span class="token interpolation-string"><span class="token string">$"/api/ShoppingCarts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">ctx<span class="token punctuation">.</span><span class="token function">OpenedShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">/products"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">AddProductRequest</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">HEADERS</span><span class="token punctuation">(</span><span class="token function">IF_MATCH</span><span class="token punctuation">(</span>expectedVersion<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre></div> <p>As you see, it already has naming and terms related to the web requests. Some may say that they should not be visible here, and I disagree, as we should be explicit in what we’re doing and how we’re doing it. That cuts the number of translations needed back and forth to understand what is this actually verifying.</p> <p>Check more in <a href="https://github.com/oskardudycz/Ogooreck">Ogooreck documentation</a>.</p> <p>Of course, syntax is a personal preference. For me, it’s readable; for you, it may be ugly.</p> <p><strong>What’s most important is that tooling is focused on the specific part of the design, software and testing it.</strong> It’s ubiquitous in all sorts of development <em>exercises</em>. Nothing even stops you from using some DSL for the acceptance tests.</p> <p>So our tooling should not be just touching the tip of the iceberg but allow us to reflect behaviour in all places keeping them focused and helping to ease development pain, not increase it.</p> <p><strong>I see Behaviour-Driven Design not as a way to write tests but how to keep the focus on the behaviour in our system.</strong> Tests are only part of it. Other parts are not less important.</p> <p>Read also:</p> <ul> <li><a href="/en/i_tested_on_production/">I tested it on production and I’m not ashamed of it</a></li> <li><a href="/en/ogooreck_sneaky_bdd_testing_framework/">Ogooreck, a sneaky testing library in BDD style</a></li> <li><a href="/en/testing_event_sourcing/">Testing business logic in Event Sourcing, and beyond!</a></li> <li><a href="/en/testing_event_driven_projections/">How to test event-driven projections</a></li> <li><a href="/en/writing_and_testing_business_logic_in_fsharp/">Writing and testing business logic in F#</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Could you help me by filling a short survey about an online course?]]>https://event-driven.io/en/online_course_survey/https://event-driven.io/en/online_course_survey/<p><strong>I realised that I could do more to help you and other folks.</strong></p> <p>I’ve been sharing online content for free in the last few years on this blog, <a href="/en/introduction_to_event_sourcing/">self-paced kits</a>, and <a href="https://github.com/oskardudycz">OSS works</a>.</p> <p>I know that it helped many folks, but…</p> <p>But I still feel I could do more and provide structured material to take you through the learning journey fully.</p> <p><strong>Of course, you can order a <a href="/en/training">workshops</a> or consultancy to get that.</strong> Yet, that’s not scaling enough, I have limited time, and I’d like to share my experience with you and more people. Giving also space for taking time and learning on your own with the preferred pace.</p> <p><strong>I prepared a short survey; I know that your time is precious, but it’ll help me a lot if you share your feedback with me:</strong></p> <div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 65%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <iframe src="https://docs.google.com/forms/d/e/1FAIpQLSfRdu5w_jMfbcuo25aWHaU75fTemMNAo6Tuuuw87zgP1VKKGA/viewform?embedded=true" frameborder="0" marginheight="0" marginwidth="0" style=" position: absolute; top: 0; left: 0; width: 100%; height: 100%; ">Loading</iframe> </div> <p><strong>Thank you a lot in advance!</strong></p> <p>I’ll also extremely appreciate resharing it with your friends or leaving comments here!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[A simple way to configure integration tests pipeline]]>https://event-driven.io/en/configure_ci_for_integration_tests/https://event-driven.io/en/configure_ci_for_integration_tests/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c7ed5de8d703a594b15600b372d87ae9/a331c/2023-08-05-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAC4jAAAuIwF4pT92AAAC9UlEQVQozwXB227aZgAAYD/VNKVCa6qsmbpuSZOlkBAIjrGDsXEM4/gDBgMGc7Ax2BhDDMYQDMGBja5pIq0XkbJdTeqkVlPaaQ+z74Pez6dLw8wF/YOmyIKsBH72e2GSpDDY3y6w94vJ498fvnz+9/Gfx/8+f/n05/3NYj7RunKBZbNFyORSd1dXPB20e6oiyUouFfG4Ujjqdx+JmeTdRfPhzfVf7+9uzf4vWtPWVbXOK6I07apdrgCtNYELB8uE/501sYYjs87t77sCZOLQDSs5YFcz1y3+xroc8rlrVVxpzXGDk9MxOXmeITFIrVZoFG2E8YlujEezWa+HYaEnji0kEEmfh3sFoFVL68XMVuorqWRyyVkpatRyJsCrEQya58kydZY6/MEUa2K5KrE5OpIWFINvDYuZ3FSVLLn21hot5cq4mhmU4pZUbAKqzcSSIRy6nw1VJp4+3l8NdEsfGhXWjxC3f3xc3DxM9OHDylpqdY1n+8Vkpwj0KiMV0moRAAKtgyj0wR4ZbDJ7/NPKMKzBaCoJMIwJmtXWF2K73xVqqlBvlTJCOtxmwnyCFFNBNk6zuDMcQCEGR7/f2uYQ90IfjLULW5W5bNa5+6Nz7wD1HGWiZBBD3Pu7iOsVeXK49+L58ctvt7e2Dp5+teF4BmHn4OnmdxxyNNX6htLVBcHWB72GyCfiGscsFR7gcPzUJdAIi/salJdF3QwBEwe7oeNXEBUBzza3K6hn0u1fKp35aHIhyZrYEvP5Ds/N60wQ9jG4r5+PNeNkK0XmT3Zq9CnjepFFXkNnVMLh2GR9LqOj9VqdYb027nQ6OVAOnXUykVE+wgQRcE7c/vbm7tL4fbUYiVV7vkifHFRAFCLo5JONb0qwc20Yk4thv8Q2YnQuFPR5ESYa7lJu2uPkKL8l5I1C4u3VXC3l1+t3KToU8rqgABV3fL1RDviUbHJaLWQJPBEDZxRwe7EwHqA9r3de7vj3ngv0qQqIARs12OSv9tI0LVnu/Q8NRI+mjBM84AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/c7ed5de8d703a594b15600b372d87ae9/a331c/2023-08-05-cover.png" srcset="/static/c7ed5de8d703a594b15600b372d87ae9/36ca5/2023-08-05-cover.png 200w, /static/c7ed5de8d703a594b15600b372d87ae9/a3397/2023-08-05-cover.png 400w, /static/c7ed5de8d703a594b15600b372d87ae9/a331c/2023-08-05-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Continuing an effort to explain DevOps scenarios, today I’d like to show you a simple way to set up integration tests that I’m using in my sample repositories and Marten.</strong></p> <p><strong>What are my assumptions?</strong> It has to be straightforward, so anyone can run it easily (including me, I’m a simple guy). I wrote why it’s important in <a href="/en/how_to_start_with_open_source/">How to get started with Open Source?</a>. It has to be stable and allow easy change or add new configuration without much additional code. Also, I like to play with different stacks and technologies. I want easy and repeatable patterns when I play with new technology. It must also run in GitHub Actions, as this is my current Continuous Integration (<em>CI</em>) runner. That’s essential, as those machines are underprovisioned and that puts limits.</p> <p><strong>I’m running many of my tests as integration ones, as I’d like to trust that what I’m testing will actually work.</strong> You can read a longer explanation of my attitude in <a href="/en/i_tested_on_production/">I tested it on production, and I’m not ashamed of it</a>. Still, this post is not about why but how.</p> <p>So, even though I’m not a massive fan of YAML, I like Docker Compose, as it <a href="/en/tricks_on_how_to_set_up_related_docker_images/">allows me to set up advanced configurations</a> in a declarative way quickly. Usually, that’s something that I only change sometimes. Once I have the default, recommended setup, I can just run <em>docker-compose up</em> and have my local environment ready.</p> <p>The benefit of Docker Compose is that most of the tools provide their container images; you can run it on any operating system and find a troubleshooting guide on the Internet.</p> <p><strong>So, how to repeat that in GitHub actions? Actually, quite simple:</strong></p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Build and Test <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token key atrule">push</span><span class="token punctuation">:</span> <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> main <span class="token key atrule">pull_request</span><span class="token punctuation">:</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Check Out Repo <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v4 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Start containers <span class="token key atrule">run</span><span class="token punctuation">:</span> docker<span class="token punctuation">-</span>compose <span class="token punctuation">-</span>f "docker<span class="token punctuation">-</span>compose.ci.yml" up <span class="token punctuation">-</span>d <span class="token comment"># Do the other steps</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Stop containers <span class="token key atrule">if</span><span class="token punctuation">:</span> always() <span class="token key atrule">run</span><span class="token punctuation">:</span> docker<span class="token punctuation">-</span>compose <span class="token punctuation">-</span>f "docker<span class="token punctuation">-</span>compose.yml" down</code></pre></div> <p>It’s a simple template, we checkout code, start containers, and in the end, we stop containers. GitHub Actions runners already have Docker preinstalled. I’m using <em>if: always()</em>; it’s needed to ensure that the cleanup step will be run even if some step fails.</p> <p>What to put in the <em>Do the other steps</em> part? In general, it’s: get dependencies, build them, and run tests. Of course, the details will depend on the environment you’re in and your application.</p> <p>See the real-world examples in:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/.github/workflows/build.dotnet.yml#L20">.NET</a></li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/.github/workflows/samples_event-sourcing-esdb-simple.yml#L35">Java</a></li> </ul> <p><strong>An important note is to trim down the Docker Compose configuration for CI.</strong> For local development, we might need containers with UIs like <a href="https://www.pgadmin.org/">PgAdmin</a>, <a href="https://www.elastic.co/kibana">Kibana</a> or tools like Open Telemetry collectors etc. For CI, we don’t need them, and they will make setup slower and can even break it by eating too many resources. We can trim it by preparing a dedicated Docker Compose config for CI, but then we need to synchronise those configs. Better is to use <a href="https://docs.docker.com/compose/profiles/">Docker Compose profiles</a>, which will allow us to exclude what we need by default and keep the configuration in a single file.</p> <p><strong>Why am I not using <a href="https://testcontainers.com/">Test Containers</a>?</strong> I’d like to and trying to do that (see <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/221">.NET</a> and <a href="https://github.com/oskardudycz/EventSourcing.NodeJS/blob/main/samples/hotelManagement/src/core/testing/eventStoreDB/eventStoreDBContainer.ts">Node.js</a> samples). Still, I’m not getting a stable enough experience overall. Why?</p> <p>TestContainers are an intriguing tool. They try to simplify container-based integration testing. The promise is that you can get or configure your docker configuration in code, and the tool will handle all initialisation, clean up etc. It should also do needed optimisations and default recommended setup.</p> <p><strong>That’s the promise, but my reality was a bit different.</strong></p> <p>Docker resources initialisation costs a lot. You need to start the image, set up networks, volumes, etc. They will run extremely slowly if you try to do it for each test. You will get isolation, of course, but at a high cost. And you can achieve isolation differently and pay less. For instance, if you’re using a relational database, you can create a new schema for a test class or even a new database. It’ll still be cheaper than spinning up a new container.</p> <p>If you run too many containers in the GitHub Actions machine, it may become infinitely idle or die. So you must be careful with your setup, as you may accidentally get false/positives after adding a new set of tests. So funnily, the more test isolation we have, the less isolated will be test run (because of eating shared resources on test runner).</p> <p>Also, if you’re spinning up a fresh ephemeral container, and the test fails, you’ll want to get your hands on it to inspect the data as you troubleshoot. If a container is cleaned up automatically by TestContainers, that’s not going to be quite so easy.</p> <p>Sometimes you also might want to use tests as the data setup for your local environment, not need to click through the UI.</p> <p>Of course, TestContainers allow you to do most of the magic, but then you need to learn the tool deeply to do that, which is kinda opposite to the premise of a seamless setup. Also, each dev environment supports a different feature set and has different documentation with not always detailed breakdowns.</p> <p><strong>So, TLDR: TestContainers are nice, but they do not match my needs so far.</strong> I’ll try to go down that path and learn more to see if I can find a workable flow with it. I’ll keep you posted.</p> <p><strong>So far, vanilla Docker Compose works best for me; it’s simple, flexible, and causes the least friction.</strong></p> <p>Read also other articles around DevOps process:</p> <ul> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/docker_compose_profiles/">Docker Compose Profiles, one the most useful and underrated features</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/marten_and_docker">How to create a Docker image for the Marten application</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to scale out Marten]]>https://event-driven.io/en/scaling_out_marten/https://event-driven.io/en/scaling_out_marten/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/92ed2c496ced77d7b4a8aa158fd94ceb/a331c/2023-07-30-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AGlrW2ZoWmNlVmFiU2BnQWqDNnmcPX6ZPYiYRoCTP26ENn2PQ5mde42Sen1+a2VhSnh3MoB9QIJ/OWhpNwB1eWd1e2l3emWMeV+FdVmanVKir1SysFy0nmObimCOm0WTq0ijlXaWln2Gemdxa02UjkGVglaWikyEhEQAhn9ljnZghHdgsZVwgW1Qjp1SmKBUn6NYu6Nug2RRmKJTmatapYxrkHRfhHRgen5eoZpZlXdeg2pOj3tfAJmRdqyHXJF0V492YaGHXI6IWpOYUZyzW93Hh+G8fZSUWHV0SaaacLSOX5J2W4J0X7Omcdu1dMywfZt4VQCrnn760Y7fwI+ohVrWt4mLaFOKi0+kslzlx5P72qWklGB3aECzpHj/2ZbUsoZ8Y0O/rXz/4KTx1KLyyIYAuKOA99Wh7c2Y8s6S07uQ5r57q7BeoqVWj3lfk4BkoJBhmZJUu6Z58dCf6MaP3rx8nItnsZd2xauB/dqiAIhzWmpeTqaOaufJmNa8kP/fpra3b6+jXWVSRDAyL4qFUKetVYl3V1xSR6aPaf/hqo51XDQyLldQQ4BvWQCQcFUxMCxVUEVmWkiOeF2eh2uQklWwpl1XPS5OSTehqFWxuF+Qc1UvLitnXk6jkmmNbUw6MipdU0U1My8Af2JBOjAiWkw9UEY7fGNNLSwucHdGpbBbW1UwV1Uuoq9OpLFXfGRBOS8gU0Q8WmA+hHtFQjghVz8rS0UuAIB9R1FOK0g7I4J8SnZZPTs1LIWZUZObVmprP1tlNoGbRZGoU4OASVBOK0o9KHyFS4aGTlhfM2RtN3iHQABzckRXYzZofDyUqlJvaDtGQiV8jktzhUVbbDZla0BtfkOMmlFxdERXYzZofDyAmElkbT1cZzlxfkh3iUYQDzYJlP9b9gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/92ed2c496ced77d7b4a8aa158fd94ceb/a331c/2023-07-30-cover.png" srcset="/static/92ed2c496ced77d7b4a8aa158fd94ceb/36ca5/2023-07-30-cover.png 200w, /static/92ed2c496ced77d7b4a8aa158fd94ceb/a3397/2023-07-30-cover.png 400w, /static/92ed2c496ced77d7b4a8aa158fd94ceb/a331c/2023-07-30-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>If you are already a reader of this blog, you probably know already that I’m not so fond of the <em>Will it scale?</em> question.</strong> I believe that, too often, it’s just a vague response if we don’t like something.</p> <p><strong>Still, I cannot deny that scaling is essential.</strong> When choosing a tool, we need to understand its limits. That’s a basis for <a href="/en/the_risk_of_ignoring_risks/">risk analysis</a> and understanding how far we can go with it, also in terms of reaching a bigger scale.</p> <p><strong>Choosing a storage tool is an essential decision.</strong> For primary usage, you must gauge the performance of the reads and writes. That can be tricky, but it’s doable. You need to think about the characteristics of the user behaviour and try to mimic that in the benchmarks. Run <a href="/en/i_tested_on_production/">synthetic tests simulating</a> production traffic and see if the performance is good enough. Better to do it for a longer time to see if there are no memory leaks or if performance degrades as time goes on. Having results, you need to verify if lags are related to your usage or the storage library per se. Then apply fixes, rinse, and repeat.</p> <p><strong>We must also <a href="/en/how_to_cut_microservices/">choose the deployment model</a> and ensure our tool matches it.</strong> And that’s not only a technical decision; it should be made based on the product strategy. If our traffic will have peaks, or if we’d like to reduce the usage of resources in the early phases, serverless can be a good decision. If we aim to iterate quickly to get validation and expect rather constant traffic, then a monolith may be the right choice for you. If you know you’ll have multiple teams; you want to invest in full autonomy, then microservices can be a decent option.</p> <p><strong>What issues can our storage tool cause to each deployment model? For instance:</strong></p> <ul> <li>in serverless we cannot have stateful or constantly-running services, and it doesn’t add a lot to the service startup,</li> <li>for the monolith, we need to ensure that it supports sharding or multi-tenancy for the single storage,</li> <li>for microservices, it can run safely with multiple clones of the same instance or newer/older versions simultaneously.</li> </ul> <p>Of course, there’s more, but those are the most important highlights.</p> <p><strong>Ok, enough theory; let’s check and evaluate Marten in those terms.</strong> We’ll use the <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/Helpdesk">Helpdesk sample</a> I presented in my NDC talk:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/jnDchr5eabI?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>It’s a simple but decent starting point for a single-deployment service.</p> <h2 id="stateless-usage" style="position:relative;"><a href="#stateless-usage" aria-label="stateless usage permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Stateless usage</h2> <p><strong>One of the foundational things to scale services is to make them stateless.</strong> If we have a temporary state (like logged-in user data, partial state of the process kept in memory, cache, etc.), we can safely replicate the instances running our service. Having such, we get more options, as we’re not tied to any deployment style.</p> <p><strong>Is Marten stateless?</strong> Yes, if you’re just doing reads and writes.</p> <p>We don’t keep anything in memory, we’re using some caches to speed up things, but they’re micro-optimisations and not the real stateful model (check <a href="https://github.com/dadhi/ImTools">ImTools</a>, they’re great). We’re also doing code generation that can happen either on-the-fly or statically upfront. But that’s another performance optimisation. Check more in <a href="/en/marten_and_docker/">my earlier article</a> to learn how to get production setup.</p> <p>So if your setup looks <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/6e495d6d77215abe4729757f9d3644b15973e299/Sample/Helpdesk/Helpdesk.Api/Program.cs#L35">like that</a>:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services <span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> options<span class="token punctuation">.</span><span class="token function">Connection</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">UseDefaultSerialization</span><span class="token punctuation">(</span> EnumStorage<span class="token punctuation">.</span>AsString<span class="token punctuation">,</span> <span class="token named-parameter punctuation">nonPublicMembersStorage</span><span class="token punctuation">:</span> NonPublicMembersStorage<span class="token punctuation">.</span>All<span class="token punctuation">,</span> <span class="token named-parameter punctuation">serializerType</span><span class="token punctuation">:</span> SerializerType<span class="token punctuation">.</span>SystemTextJson <span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LiveStreamAggregation</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Incident<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentDetailsProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentShortInfoProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentHistoryTransformation<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">OptimizeArtifactWorkflow</span><span class="token punctuation">(</span>TypeLoadMode<span class="token punctuation">.</span>Static<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseLightweightSessions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then you’re free to go.</p> <p>For sure, you noticed this line.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentDetailsProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Such lines are the registration of projections. Projections allow to build read models from appended events. They can be either <em>inline</em> or <em>async</em>. Inline ones are evaluated in the same transaction as the appended event, async in the background process (read more in <a href="/en/projections_in_marten_explained/">Event-driven projections in Marten explained</a>).</p> <p>As I registered all projections as inline, we’re still stateless, as they will be <em>just</em> changing more records in the database. Read models are stored as Marten documents.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a1581e7ea52be15f75a57488253da411/a331c/2023-07-30-image-1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAA80lEQVQoz6XSsU7DMBAG4DwZb8Q7MPMEbAxdGBhBKipCLCAGRiRCHaWR7eI0hiYXp07SxHcoRaoqVKpE/XWDT7pPvuE8OiLe9qWUklKGYRjHMeecBYGQYhEvGAsYY5wLIUQURVrrPdgYAwBaa2stZGmaLvMcytICZEmSZJsAQFEUe/BuGkeIXa1bbF2PtXfzrqrRi7p++76bgrFIRNgf+5/V7WQ29pcPgclXQzASlWsEU0v5VTdUN27Q2t03xWo+ZeOuQRyML58fz24uRF4d8N4/lk6vXk/O7z+yDrv++HfQ1+ZJpnWLvS7sqPP8E4foEA/jH9cH9sSDtdjbAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="image" title="image" src="/static/a1581e7ea52be15f75a57488253da411/a331c/2023-07-30-image-1.png" srcset="/static/a1581e7ea52be15f75a57488253da411/36ca5/2023-07-30-image-1.png 200w, /static/a1581e7ea52be15f75a57488253da411/a3397/2023-07-30-image-1.png 400w, /static/a1581e7ea52be15f75a57488253da411/a331c/2023-07-30-image-1.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We’ll get to async projections and their impact soon.</p> <h2 id="serverless" style="position:relative;"><a href="#serverless" aria-label="serverless permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Serverless</h2> <p>There are a few things here. .NET, in general, is not an excellent match for serverless. It has a cold start issue related to virtual machine warm-up. Each .NET edition is trying to help in that .NET 8 will bring <a href="https://learn.microsoft.com/en-us/dotnet/core/deploying/native-aot/?tabs=net7">Native AOT</a>, but it’s still not enough. Native AOT is still experimental, and even on .NET. 8, it lacks tree shaking, so the executable size will be big.</p> <p>Marten doesn’t support Native AOT yet (we will at some point), but I don’t see Marten being worse than other .NET solutions if you’re using pre-built types. So if you’re considering .NET serverless and applied the <a href="/en/marten_and_docker/">guidance around production setup</a>, Marten should do good for you. Or even better! Read more in <a href="https://jeremydmiller.com/2022/05/31/marten-just-got-better-for-cqrs-architectures/utm_source=event_driven_io">Jeremy’s article Marten just got better for CQRS architectures</a>.</p> <p>There’s one more thing here. If you’re afraid of cold starts, and you’d like to have lambdas/functions warm constantly, then you should consider whether serverless is a good hosting model. You’ll pay much more for constantly warm lambdas/functions than regular containerised or virtual machine deployments. Especially if you can them turn off at a defined time of the day or scale down, look for solutions like AWS Fargate or Azure Container Apps.</p> <h2 id="scaling-async-projections" style="position:relative;"><a href="#scaling-async-projections" aria-label="scaling async projections permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Scaling async projections</h2> <p><strong><a href="https://martendb.io/events/projections/async-daemon.html#async-projections-daemon">Async Daemon</a> is the Marten subsystem responsible for processing projections asynchronously.</strong> Technically, it’s a background process running as hosted service that processes events and stores results as read models. We recommend using it for more resource-demanding, multiple-stream projections, <a href="/en/projecting_from_marten_to_elasticsearch/">storing results in other database types</a> or <a href="/en/integrating_Marten/">integration with other systems</a>.</p> <p><strong>Technically, it’s a sneaky service that polls events in batches for each projection type, runs projections simultaneously, <a href="https://martendb.io/events/projections/async-daemon.html#error-handling">ensures resiliency and fault tolerance</a>, and other optimisation like gap detection you wouldn’t want to implement on your own.</strong></p> <p>It can run in <a href="https://martendb.io/events/projections/async-daemon.html#solo-vs-hotcold">two modes</a> currently:</p> <ul> <li><em>Solo</em>, which assumes that there will always be only a single instance running for the specific event storage.</li> <li><em>HotCold</em>, which can deal with multiple instances targeting the same event storage. Yet, only one will be active. Marten will perform leader election, ensuring that only a single instance will actively process data.</li> </ul> <p><strong>So if you’re running a single instance monolith, you may consider <em>solo</em> mode not to perform additional traffic related to leader election. For any other, use <em>hot cold</em> mode as it will be just safer.</strong></p> <p>Especially in containerised environments like <strong>Kubernetes</strong>, that’s essential. You can spin up a few instances of the same service running Async Daemon, and by that, increasing resiliency if the instance dies randomly. Yet, you should ensure that doubled instances won’t be killed if they get failures related to leader election. Health checks and containerised environments experience is something that we’re planning to enhance soon. Actually, we have an <a href="https://github.com/JasperFx/marten/pull/2648">in-progress pull request</a> from the external contributor that should soon help with that.</p> <p><strong>So, by default, you may have multiple instances using hot cold, but for resiliency, not for processing speed improvement.</strong> I explained in <a href="/en/how_to_scale_projections_in_the_event_driven_systems/">How to scale projections in the event-driven systems?</a> that batched, and parallelised single-instance processing should take you far. You should be fine as long as you can process events faster than they’re coming. Especially that you can fall back to inline projections if you need strong consistency.</p> <p>What if you can’t? We’ll get to that.</p> <h2 id="splitting-readswrites-from-async-daemon" style="position:relative;"><a href="#splitting-readswrites-from-async-daemon" aria-label="splitting readswrites from async daemon permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Splitting reads/writes from Async Daemon</h2> <p>As mentioned, we can quickly scale instances horizontally for stateless usage. We cannot do that for Async Daemon easily. What can we do if we have such a configuration?</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services <span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>sp <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StoreOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> schemaName <span class="token operator">=</span> Environment<span class="token punctuation">.</span><span class="token function">GetEnvironmentVariable</span><span class="token punctuation">(</span><span class="token string">"SchemaName"</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token string">"Helpdesk"</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Events<span class="token punctuation">.</span>DatabaseSchemaName <span class="token operator">=</span> schemaName<span class="token punctuation">;</span> options<span class="token punctuation">.</span>DatabaseSchemaName <span class="token operator">=</span> schemaName<span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">Connection</span><span class="token punctuation">(</span>builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">.</span><span class="token function">GetConnectionString</span><span class="token punctuation">(</span><span class="token string">"Incidents"</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">UseDefaultSerialization</span><span class="token punctuation">(</span> EnumStorage<span class="token punctuation">.</span>AsString<span class="token punctuation">,</span> <span class="token named-parameter punctuation">nonPublicMembersStorage</span><span class="token punctuation">:</span> NonPublicMembersStorage<span class="token punctuation">.</span>All<span class="token punctuation">,</span> <span class="token named-parameter punctuation">serializerType</span><span class="token punctuation">:</span> SerializerType<span class="token punctuation">.</span>SystemTextJson <span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LiveStreamAggregation</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Incident<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentHistoryTransformation<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentDetailsProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentShortInfoProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CustomerIncidentsSummaryProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">KafkaProducer</span><span class="token punctuation">(</span>builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">)</span><span class="token punctuation">,</span> ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SignalRProducer</span><span class="token punctuation">(</span><span class="token punctuation">(</span>IHubContext<span class="token punctuation">)</span>sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IHubContext<span class="token punctuation">&lt;</span>IncidentsHub<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ProjectionLifecycle<span class="token punctuation">.</span>Async <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> options<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">OptimizeArtifactWorkflow</span><span class="token punctuation">(</span>TypeLoadMode<span class="token punctuation">.</span>Static<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseLightweightSessions</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddAsyncDaemon</span><span class="token punctuation">(</span>DaemonMode<span class="token punctuation">.</span>Solo<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>As you see, we have both transaction processing and asynchronous projections. This is fine for the simpler application, but for the higher throughput, it may be worth <strong>Splitting reads/writes from Async Daemon</strong>. We must also switch daemon from <em>solo</em> to <em>hotcold</em>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b3b99ed392d40b05e16c2e24e98327f4/a331c/2023-07-30-image-2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABZklEQVQoz42SS07DMBCGuR6n4BLcgR13YAsbRMWmQoiHumXDo5QmcUJSu47rR5LGSeoXatNKBaKo/2LkGfmbmV/2kduTMUZrrZSy1rbnNrZFY4z7raP9JAiC8UZhGEIIkyTxfT+OYwih7/sIoW7YWuucE0J4njefzxFCAAAIIcYYABDHcRRFnPO+yXVdM8aKoijLMk1TIUSe54wxSinnXErZAduN2htVJYUQsqra+sasMmZtvmmaP7bXsJSSMUYISTFOyWKREkoIZVwpLbJ8WSzzLBdZLoTQWnes3basVwZg9egVd5MswIoWChAzCsuXpI6IabTt81w1xiPudoQGz7OAOVroKbZXH3z4lXlIVyvdBytthHRRQr9nDDHJM4loOQ1xymu+NNr0Tm4fTMqSURJ8vvuTMWcUodn/79EBr/ldGL5dPk1vtj239YNgJxp9fH59cnFf2d0+h8MrbU8Hr2cPnnF9+gH5AuwFYag3TQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="image" title="image" src="/static/b3b99ed392d40b05e16c2e24e98327f4/a331c/2023-07-30-image-2.png" srcset="/static/b3b99ed392d40b05e16c2e24e98327f4/36ca5/2023-07-30-image-2.png 200w, /static/b3b99ed392d40b05e16c2e24e98327f4/a3397/2023-07-30-image-2.png 400w, /static/b3b99ed392d40b05e16c2e24e98327f4/a331c/2023-07-30-image-2.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Let’s define the general helpers that will help us to reuse registrations with the production-grade setup:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Configuration</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">MartenServiceCollectionExtensions<span class="token punctuation">.</span>MartenConfigurationExpression</span> <span class="token function">AddMartenWithDefaults</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> connectionString<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> schemaName<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>StoreOptions<span class="token punctuation">,</span> IServiceProvider<span class="token punctuation">></span></span> configure <span class="token punctuation">)</span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>sp <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StoreOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> schemaName <span class="token operator">=</span> Environment<span class="token punctuation">.</span><span class="token function">GetEnvironmentVariable</span><span class="token punctuation">(</span><span class="token string">"SchemaName"</span><span class="token punctuation">)</span> <span class="token operator">??</span> schemaName<span class="token punctuation">;</span> options<span class="token punctuation">.</span>Events<span class="token punctuation">.</span>DatabaseSchemaName <span class="token operator">=</span> schemaName<span class="token punctuation">;</span> options<span class="token punctuation">.</span>DatabaseSchemaName <span class="token operator">=</span> schemaName<span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">Connection</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">UseDefaultSerialization</span><span class="token punctuation">(</span> EnumStorage<span class="token punctuation">.</span>AsString<span class="token punctuation">,</span> <span class="token named-parameter punctuation">nonPublicMembersStorage</span><span class="token punctuation">:</span> NonPublicMembersStorage<span class="token punctuation">.</span>All<span class="token punctuation">,</span> <span class="token named-parameter punctuation">serializerType</span><span class="token punctuation">:</span> SerializerType<span class="token punctuation">.</span>SystemTextJson <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">configure</span><span class="token punctuation">(</span>options<span class="token punctuation">,</span> sp<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> options<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">OptimizeArtifactWorkflow</span><span class="token punctuation">(</span>TypeLoadMode<span class="token punctuation">.</span>Static<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseLightweightSessions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddMartenAsyncOnly</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> connectionString<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> schemaName<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>StoreOptions<span class="token punctuation">,</span> IServiceProvider<span class="token punctuation">></span></span> configure <span class="token punctuation">)</span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token function">AddMartenWithDefaults</span><span class="token punctuation">(</span> connectionString<span class="token punctuation">,</span> schemaName<span class="token punctuation">,</span> configure <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddAsyncDaemon</span><span class="token punctuation">(</span>DaemonMode<span class="token punctuation">.</span>HotCold<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The first method sets up the default setting; the second adds the <em>AsyncDaemon</em> setup.</p> <p>We can reuse those methods in our module setup:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">StorageConfiguration</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddMartenForHelpdeskInlineOnly</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name">IConfiguration</span> configuration <span class="token punctuation">)</span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token function">AddMartenWithDefaults</span><span class="token punctuation">(</span> configuration<span class="token punctuation">.</span><span class="token function">GetHelpdeskConnectionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> HelpdeskSchemaName<span class="token punctuation">,</span> <span class="token punctuation">(</span>options<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LiveStreamAggregation</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Incident<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentHistoryTransformation<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentDetailsProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentShortInfoProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddMartenForHelpdeskAsyncOnly</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name">IConfiguration</span> configuration<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>StoreOptions<span class="token punctuation">,</span> IServiceProvider<span class="token punctuation">></span></span> configure <span class="token punctuation">)</span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token function">AddMartenAsyncOnly</span><span class="token punctuation">(</span> configuration<span class="token punctuation">.</span><span class="token function">GetHelpdeskConnectionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> HelpdeskSchemaName<span class="token punctuation">,</span> <span class="token punctuation">(</span>options<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CustomerIncidentsSummaryProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">KafkaProducer</span><span class="token punctuation">(</span>builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">)</span><span class="token punctuation">,</span> ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SignalRProducer</span><span class="token punctuation">(</span><span class="token punctuation">(</span>IHubContext<span class="token punctuation">)</span>sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IHubContext<span class="token punctuation">&lt;</span>IncidentsHub<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ProjectionLifecycle<span class="token punctuation">.</span>Async <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">GetHelpdeskConnectionString</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IConfiguration</span> configuration<span class="token punctuation">)</span> <span class="token operator">=></span> configuration<span class="token punctuation">.</span><span class="token function">GetConnectionString</span><span class="token punctuation">(</span>IncidentsConnectionStringName<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> HelpdeskSchemaName <span class="token operator">=</span> <span class="token string">"Helpdesk"</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> IncidentsConnectionStringName <span class="token operator">=</span> <span class="token string">"Incidents"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>By that, we have different setups for the operation’s needs, so writes/reads and background processing, more resource-demanding projections, SignalR notifications and forwarding events to Kafka.</strong></p> <p>This enables us to scale operations horizontally. We can also add resiliency to the Async Daemon and scale it vertically independently. Thanks to that, we could use smaller boxes or even serverless for regular processing and a bigger box for asynchronous projections.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a5ed0822142f3741db972a369d6e100c/a331c/2023-07-30-image-3.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAB2klEQVQoz42S247TMBCG87S8A3e8Au+wcLfcIC52JUCLBIjDqntq002TNAfXdtLE3jgbx+eg1lVZIZD4rmY0h3/GnsA5Nz3h6DrnrLXecM4ZY3zoaX4AIYyiKM9zAABCCEK4Xq/TNM3zPI7juq4BAGVZgj1FUSyXy3SPtTYwxjRNUxQFhBAAgDEmhCRJstnjQ74Fxth3QQgJIXbK0zQxxjDGXdfVdY0QYoz5ERBChJCqqhhj5Z4sy5qmQQj54YPjApQ+UEp9y6copSBEXhCADSH0uHYghDBKVBiBMsdoN3rfPSgltBRSCmtt27YVRu22ghtQYVgU+TAMh+KBi6o3dNAtt4TbdjB1r9te4V5LpYUYOR9T1C1LShnfdpL2auT8MPYgzMnH8kfC3l3WZ7Pt+7v29Cv6Fj++vgBc2pEPgo/nS/rqpqla8Sakn/PeKeV/MXjkcr6oN1uxWtMM9GnJooSCWizCrbJOilFIeZ2QL2FdkeEe86IVfBisVzbGgE0ZRdHN7XW0iubzuzBcxPFqNrsscwAKmGXZ1exqFa2M0VpKKcXxUQN/NFprdWBnSCmnafo5//A9fDtNkzH6j0P8Xfw3dqnPTz89OznTR/8/i73My4v4xfm9m/7JL1xV2e57kRJAAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="image" title="image" src="/static/a5ed0822142f3741db972a369d6e100c/a331c/2023-07-30-image-3.png" srcset="/static/a5ed0822142f3741db972a369d6e100c/36ca5/2023-07-30-image-3.png 200w, /static/a5ed0822142f3741db972a369d6e100c/a3397/2023-07-30-image-3.png 400w, /static/a5ed0822142f3741db972a369d6e100c/a331c/2023-07-30-image-3.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="sharding-async-daemon" style="position:relative;"><a href="#sharding-async-daemon" aria-label="sharding async daemon permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Sharding Async Daemon</h2> <p>And here, kids, we’re getting into the <em>don’t try this at home</em> zone.</p> <p><strong>As <a href="https://github.com/JasperFx/marten/discussions/2550">Jeremy explained in Critter Stack 2023 Game Plan</a>, scaling Async Daemon will be one of the biggest challenges and the highest priorities for us in the upcoming releases.</strong> We understand the importance of that for the systems with ultimate throughput needs. We’ll get it, but even for now, there are some options to shard processing. Yet, that requires a bit of hacking.</p> <p><strong>Marten uses <a href="https://vladmihalcea.com/how-do-postgresql-advisory-locks-work/">Postgres advisory locks</a> for leader election.</strong> We’re using it for distributed locking, as they’re fast, lightweight and reliable. Jeremy explained that in detail in his article <a href="https://jeremydmiller.com/2020/05/05/using-postgresql-advisory-locks-for-leader-election/">Using Postgresql Advisory Locks for Leader Election</a>.</p> <p><strong>Having a single shard advisory lock with a known id helps ensure that only a single one will be active for the group of nodes.</strong> There won’t be clashing updates to read models, competing consumer issues etc. Marten internally stores the progression of the specific projection type as a row in the <em>mt_event_progression</em> table. Each projection type has its row with the number of the last processed event sequence number (read more about positions in event stores in my <a href="/en/lets_talk_about_positions_in_event_stores/">another article</a>).</p> <p><strong>What if we could distribute the processing of projection types exclusively between nodes?</strong></p> <p>Actually, with a bit of hacking, we can do that. Fasten your seatbelt, and let’s take a ride!</p> <p><strong>Marten has (intentionally) an undocumented feature that allows you to specify the advisory lock id that Async Daemon will use.</strong> That means that you may have more than one group of Async Daemon nodes targeting the same set of events.</p> <p><strong>That can be dangerous if they are processing the same projection types.</strong> They would be clashing with each other and causing random failures like projections results overrides, progress tracking misalignments etc. I guarantee you want to avoid ending up with such issues!</p> <p><strong>But if you distribute projection types exclusively, that can actually work.</strong> How to do that?</p> <p>Let’s expand our helper method:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Configuration</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddMartenAsyncOnly</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> connectionString<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> schemaName<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>StoreOptions<span class="token punctuation">,</span> IServiceProvider<span class="token punctuation">></span></span> configure<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> daemonLockId <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token function">AddMartenWithDefaults</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> schemaName<span class="token punctuation">,</span> <span class="token punctuation">(</span>options<span class="token punctuation">,</span> sp<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>daemonLockId<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span>DaemonLockId <span class="token operator">=</span> daemonLockId<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token function">configure</span><span class="token punctuation">(</span>options<span class="token punctuation">,</span> sp<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddAsyncDaemon</span><span class="token punctuation">(</span>DaemonMode<span class="token punctuation">.</span>HotCold<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>I added an optional parameter allowing you to pass a daemon lock id.</p> <p>Let’s update our Helpdesk helper method also:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">StorageConfiguration</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddMartenForHelpdeskAsyncOnly</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name">IConfiguration</span> configuration<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>StoreOptions<span class="token punctuation">,</span> IServiceProvider<span class="token punctuation">></span></span> configure <span class="token punctuation">)</span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token function">AddMartenAsyncOnly</span><span class="token punctuation">(</span> configuration<span class="token punctuation">.</span><span class="token function">GetHelpdeskConnectionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> HelpdeskSchemaName<span class="token punctuation">,</span> configure<span class="token punctuation">,</span> configuration<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetValue</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">HelpdeskSettingsSectionName</span><span class="token punctuation">}</span></span><span class="token string">:</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">DaemonLockIdSectionName</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> HelpdeskSettingsSectionName <span class="token operator">=</span> <span class="token string">"Helpdesk"</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> DaemonLockIdSectionName <span class="token operator">=</span> <span class="token string">"DaemonLockId"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>I’m reading the lock id from settings, but you could also pass it explicitly.</p> <p><strong>Having such registration, we could have the following projects and services structure:</strong></p> <ul> <li><em>Helpdesk.API</em> - operations scaled horizontally, with read, writes and inline projections. Registration would look like:</li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span>Services <span class="token punctuation">.</span><span class="token function">AddMartenForHelpdeskInlineOnly</span><span class="token punctuation">(</span>builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <ul> <li><em>Helpdesk.Projections</em> - Async Daemon instance processing async projections building read models. Potentially we could shard it even more if we have some demanding projections.</li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span>Services <span class="token punctuation">.</span><span class="token function">AddMartenForHelpdeskAsyncOnly</span><span class="token punctuation">(</span> builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">,</span> <span class="token punctuation">(</span>options<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=></span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CustomerIncidentsSummaryProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <ul> <li><em>Helpdesk.SignalR</em> - Async Daemon instance republishing notifications about events.</li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span>Services <span class="token punctuation">.</span><span class="token function">AddMartenForHelpdeskAsyncOnly</span><span class="token punctuation">(</span> builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">,</span> <span class="token punctuation">(</span>options<span class="token punctuation">,</span> sp<span class="token punctuation">)</span> <span class="token operator">=></span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SignalRProducer</span><span class="token punctuation">(</span><span class="token punctuation">(</span>IHubContext<span class="token punctuation">)</span>sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IHubContext<span class="token punctuation">&lt;</span>IncidentsHub<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ProjectionLifecycle<span class="token punctuation">.</span>Async <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <ul> <li><em>Helpdesk.Kafka</em> - Async Daemon instance republishing external/integration events to Kafka.</li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span>Services <span class="token punctuation">.</span><span class="token function">AddMartenForHelpdeskAsyncOnly</span><span class="token punctuation">(</span> builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">,</span> <span class="token punctuation">(</span>options<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=></span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">KafkaProducer</span><span class="token punctuation">(</span>builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">)</span><span class="token punctuation">,</span> ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Each Async Daemon instance will need a different lock id (e.g. 4444, 5555, 6666).</p> <p><strong>Such config will work even if we have multiple instances of those services, as within the group of instances, leader election will be made, and a single instance of the specific service will be running and processing a distinct subset of projections.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/71579695911d5132542078be5e044f4d/a331c/2023-07-30-image-4.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAByUlEQVQoz3WSS4/TMBCA89/4hxw4cePKqUjsASTEigMHtqttS1u1TbdN4jqJnTiJX/Eb2rBVW+A7jCzPfDOWxlF4wXuvtTbG9H1vjHHOaa2ttcPBnLDWhguiQQshcM4Xi0Ucx6vVKo7jNE3jOF6v1wih9YnNZgMAuJXPk4cuIQTnnLXWe+9OnLPngiv5fHuZvin9mz+ytVYIIaW8kYUQfd9rrYex8oQQQimtlIqC90r1EMIk2QMAMMbGaO+PT2WMpUmS53mWpU1DQgiHAwAggxACkNV1HUmlGRctZZQJymXTMSEVF1IpRZq2IrRjnDFBKeecMy46eoykoaRpo7unepzwT9N6vBcfn/AklaMxzioVnEowfz8nqDP3OzYG3Eo+K+TnbVc05m5DlxmJHib5rugfZ2UM+OMMbQ/yYVKWtfROZyX9uqyqVk2hXJVSCZbW6mfeo1Z9WVarBEe73bMQfLuNMUa7/TPGaL/f4RJpoTBC08kEIcQp7dqmJqRtW9p1dV0v5/Ou7SJKKYQwhzDP8yFWqPox/f5u9KascFEU+QuXZ0LI1ScZMO64qrfflq9ef2BuWNj/9+yvcSd5UdDRHB0bee/8v/kt/wKPTeFS24bMeAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="image" title="image" src="/static/71579695911d5132542078be5e044f4d/a331c/2023-07-30-image-4.png" srcset="/static/71579695911d5132542078be5e044f4d/36ca5/2023-07-30-image-4.png 200w, /static/71579695911d5132542078be5e044f4d/a3397/2023-07-30-image-4.png 400w, /static/71579695911d5132542078be5e044f4d/a331c/2023-07-30-image-4.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Of course, such configuration is something that you can try when you know that previously described scaling techniques are not enough. You should be careful with applying it, but it can be a solution until we deliver more sophisticated mechanisms out of the box.</p> <p>See also full sample showing that in the <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/219">Pull Request</a>.</p> <h2 id="tldr" style="position:relative;"><a href="#tldr" aria-label="tldr permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>TLDR</h2> <p>I hope this article will give you a good understanding of your scaling options with Marten. Marten can take you pretty far, even with a basic setup. I didn’t focus on that, but we have built-in multi-tenancy that can offload you a lot. Jeremy explained that neatly in <a href="https://jeremydmiller.com/2022/03/29/working-with-multiple-marten-databases-in-one-application/">Working with Multiple Marten Databases in One Application</a>.</p> <p><strong>Here are summary of steps of how to gradually scale services using Marten:</strong></p> <ol> <li>Start with a single instance.</li> <li>Split instances per module.</li> <li>Split operations (read/writes and inline projections) from Async Daemon and run them separately.</li> <li>Scale operations service horizontally.</li> <li>Use multi-tenancy and scale it even more horizontally.</li> <li>Scale Async Daemon vertically.</li> <li>Shard projections processing of Async Daemon (ensuring each shard has a distinct set of projections and daemon lock id).</li> </ol> <p><strong>If you want to get help in scaling your event-driven solutions <a href="mailto:[email protected]">contact me!</a>.</strong> Check also my <a href="/en/training/">training</a> page.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to set global setting for XUnit tests]]>https://event-driven.io/en/xunit_global_settings/https://event-driven.io/en/xunit_global_settings/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6bbf576105954136cddb400a0f263bc1/a331c/2023-07-23-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AOjm4eLe1eLe1+Xh2Obh2+Tg2+Xh2+bh3OTf3Ofj4OTg3ePe2uPf2/Lx7/Lw8PHv7/Lw7/Xz8+jl5Orn5gCjkGiPcS2XezqaeTuTb0GWb0qcdEyZclaacVqbeWWUdGKNYU3Ar6bn5uTp5eTs6efo5ePn5uTa1NKmjY4AsaBwl4cwnZA8o5k0tq4stKAxiXI2jHZFrJBJpI5PmXVejGFJtaKY3drd49/h7uzt5uLk49/fxri8l4GFAMrPdLG6NoSDOnVxMKSlIujtLXp1OY6JQurmUM/MX8W6er+jZJdlO7athNraps7LuNPYvbu4rJSJd6+ikgDLyV28tyG/mCqjdSOjeiXSzDOdmDGjnTLq3Da1m0fEr2S8lEyfYR3VqFDj35vNypTi56DIz5zTwHDYvY4A08RXy7sf67slh18cn3dElnQwvbAz0Lw13MI50Kg7x6A4qHwtxJ1bqoxlgHhi1dCHw7Bu1bFV5bFF4cKJANzFX+jXNNi+SphtItuaIs+qRLqjTcapRtOxUevBXJqBK0w5Foh8Z31oRKl+Mk41EmlQIvbHUv7LSujGfwDny1H02ijTs161dTOpZR66k0PSrk3WnkLZo0nZrV9yYDFbOgzgmSqtdC3GkDOpfC7ZrUv80GLsu0Tnx3YA6slM+c8Yy61Xpm9BdEQkg1Ip0JhH16dC37lByq5DuIBBv3kk3YknvXQmn3Y65rxZ/9NS27ZdtJVmv62MAOC7WfO7FuLCS7uLUp9mPKhwPcCQROjJLOvQKOHDLryJQrN6Ib96JeKNKMuMLe24NeStOs2cWtWna8KhgADv59nv4sfp387h2NHk2dDl2dHp4dL068rw5cbu5s7n3tHj2crm28np3Mrn3c3v5Mzj18vl1s3k1sjw5tzn35vTpledeAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/6bbf576105954136cddb400a0f263bc1/a331c/2023-07-23-cover.png" srcset="/static/6bbf576105954136cddb400a0f263bc1/36ca5/2023-07-23-cover.png 200w, /static/6bbf576105954136cddb400a0f263bc1/a3397/2023-07-23-cover.png 400w, /static/6bbf576105954136cddb400a0f263bc1/a331c/2023-07-23-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>XUnit is not my favourite testing tool; I already mentioned that in <a href="/en/how_to_setup_a_test_matrix_in_xunit/">How to set up a test matrix in XUnit?</a>.</strong> To be fair, none of the .NET test tooling is my favourite. They’re hard to customise and run more advanced configurations or performance exhausting tasks.</p> <p>I heard good things about <a href="https://github.com/fixie/fixie">Fixie</a>, but I’m not even sure if it’s actively maintained. And here’s the thing, testing framework is rarely the hill you’d like to die on. We should select it based on the team’s preferences, as they said: <em>“When in Rome, do as the Romans do”</em>. Although, in this case, it’s more <a href="https://www.youtube.com/watch?v=Cv6tuzHUuuk">walking like Egyptians</a>.</p> <p>Still, enough of this rant; let’s look at the customisation I brought you today.</p> <p><strong>Let’s say we’d like a global setup before running all tests in XUnit; for instance, set some settings or environment variables.</strong></p> <p>To do that, we need to follow the best object-oriented practices and create the following class:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">namespace</span> <span class="token namespace">Your<span class="token punctuation">.</span>Test<span class="token punctuation">.</span>Project<span class="token punctuation">.</span>Namespace</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">AssemblyFixture</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">XunitTestFramework</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">AssemblyFixture</span><span class="token punctuation">(</span><span class="token class-name">IMessageSink</span> messageSink<span class="token punctuation">)</span> <span class="token punctuation">:</span><span class="token keyword">base</span><span class="token punctuation">(</span>messageSink<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Do your setup here</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We also need to align with the best C# practices and sprinkle that with a bit of attributes magic, and add that before the class declaration:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">[assembly: TestFramework("Your.Test.Project.Namespace.AssemblyFixture", "Your.Test.Project.AssemblyName")]</code></pre></div> <p>Then place this file in the test project.</p> <p>**Could I show you a real-world example?” Yes, I can! Last week <a href="/en/marten_and_docker/">I explained how Marten code generation works</a>. We call it through the command line. Marten uses <a href="https://jasperfx.github.io/oakton/guide/getting_started.html">Oakton</a> for command line handling, so the last line in our application has to look like that.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">return</span> <span class="token keyword">await</span> app<span class="token punctuation">.</span><span class="token function">RunOaktonCommands</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That’ll automatically start the web application, but unfortunately, not in tests. To do that, we need to instrument Oakton to autostart the host. We can do that by setting the following variable:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">OaktonEnvironment<span class="token punctuation">.</span>AutoStartHost <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span></code></pre></div> <p>You could use another project from the Critter Stack <a href="https://jasperfx.github.io/alba/guide/gettingstarted.html">Alba</a> that’d do it for you.</p> <p><strong>But I’m not using it, as I’m using my own <a href="https://github.com/oskardudycz/Ogooreck">Ogooreck</a>, which is a thin wrapper on .NET testing tooling that helps you cut boilerplate in Behaviour-Driven Design style.</strong></p> <p>What to do, then? Of course, I could use the class defined above to set this variable. The whole code would look like</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token target keyword">assembly</span><span class="token punctuation">:</span> <span class="token class-name">TestFramework</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"Your.Test.Project.Namespace.AssemblyFixture"</span><span class="token punctuation">,</span> <span class="token string">"Your.Test.Project.AssemblyName"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">namespace</span> <span class="token namespace">Your<span class="token punctuation">.</span>Test<span class="token punctuation">.</span>Project<span class="token punctuation">.</span>Namespace</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">AssemblyFixture</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">XunitTestFramework</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">AssemblyFixture</span><span class="token punctuation">(</span><span class="token class-name">IMessageSink</span> messageSink<span class="token punctuation">)</span> <span class="token punctuation">:</span><span class="token keyword">base</span><span class="token punctuation">(</span>messageSink<span class="token punctuation">)</span> <span class="token punctuation">{</span> OaktonEnvironment<span class="token punctuation">.</span>AutoStartHost <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>This code will ensure the setting is applied once at the beginning of tests.</p> <p><strong>Be careful with it!</strong> This approach works well for the basic global setup that should apply to all tests. You should not do exhaustive initialisation like database setup. As you see, it allows only synchronous code, and we’re plugging into the XUnit bare bones. This code will be run during test discovery in the IDE etc. As it’s global code, remember it may impact your test isolation. This is fine for our case, as we want to change the global setting for all tests, but it might not be fine for your case.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to create a Docker image for the Marten application]]>https://event-driven.io/en/marten_and_docker/https://event-driven.io/en/marten_and_docker/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4ddeb79f4c6f57c79c116e7a3f3db9a0/a331c/2023-07-13-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABj0lEQVQoz2P4jwr+/QOhP3///f377z8hwIDM+fv3z79/f/7/+4Ni2r+/f//8wqf5D9ie6Tuv5i2/OnXb5embTq/cefrVw9uvn9x//+bljx9fv33/+vHdm79//mDR/PcfSPOZm0+la89ULjyslL2aLW6VY9Muq8nXKzoXHZ3evrmrYXq459X9O0GO+fsXi7N/f34TOvO87uRHVhOveUw8Izv1o8S8/yFZfS3WKnMT/bvCHC8c2o1d87///3+9exo356zg1J/2M+9bV6xV6H0kN/2zYt+jhV21W1oj989t+vDq2e+f38FqUTWDvP3r85o9p8Xb74lP+WK48Gv59ueeM67bNmzeumX71s603fM7Hz24//fXD+yhDfb475l7bjrNulux5/3Mcz8KFh3JnLRowsL1Ny+eXL1kwe7li+BuRtcM1f3je0P39PC81m1HLq3adSymcUbz4m2///5fvWHbyUOHILGHy+b/nz5/XrJ669KVGz99eA/ifvp84sL1sxeuvP/wERyh/3DYjJ7aCKQzAKscOonsGy02AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4ddeb79f4c6f57c79c116e7a3f3db9a0/a331c/2023-07-13-cover.png" srcset="/static/4ddeb79f4c6f57c79c116e7a3f3db9a0/36ca5/2023-07-13-cover.png 200w, /static/4ddeb79f4c6f57c79c116e7a3f3db9a0/a3397/2023-07-13-cover.png 400w, /static/4ddeb79f4c6f57c79c116e7a3f3db9a0/a331c/2023-07-13-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Containerisation is something that pushed our industry much further.</strong> Generating immutable artefacts is a foundational aspect of the DevOps process. It’s much easier to troubleshoot, diagnose and catch earlier production issues. We can assume that no cowboy updates were involved.</p> <p>I wrote already a few guides on this process:</p> <ul> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a>.</li> </ul> <p>Even though I’m mostly connected to the Event-Driven approach, they’re one of the most read articles on my blog, proving its importance.</p> <p><strong>I feel that the DevOps process in Event Sourcing tools is not appreciated enough; we talk too less about that, especially on the operations, continuous integration and delivery.</strong> That may be a reason why this is still a niche. People are afraid of how to run such systems in production.</p> <p><strong>Today, Let’s bury this hole a bit and explain how to build a Docker image of the application running Marten as an event store.</strong></p> <p>To run Marten on prod, you must have a Postgres database instance. Most of the cloud providers support them as managed services. You can run it on your own on Virtual Machine, on-premise or container. I’ll assume that you have such.</p> <p>You also need to have a deployment for your application running Marten, for instance, Kubernetes, a container-based, virtual machine running docker images. Choose what suits your case and your preferences.</p> <p><strong>Whatever that is, you’ll need to build a container image. That config will need to include some of the Marten specifics. Which one?</strong></p> <p><strong>In Marten, we’re using code generation extensively.</strong> It helps us to cut the number of allocations, as instead of using reflection a lot, and dictionary lookup, we’re building the <a href="https://en.wikipedia.org/wiki/Matryoshka_doll">Matryoshka doll model</a> on top of your code to optimise the performance and resiliency.</p> <p>Marten can generate code on the fly when you call it, which is helpful for the development phase, but may add overhead for regular usage, as it may increase <em>the cold start</em> of the application code. For production, you’d like to cut all overhead. We understand that, so we allow you to pre-built generated code and use static code instead of doing it on the fly.</p> <p><strong>You can <a href="https://martendb.io/configuration/prebuilding.html">read about that in our docs</a>, but in short, you need to do a few steps:</strong></p> <ol> <li>Add <a href="https://martendb.io/configuration/cli.html">Marten.CommandLine NuGet package</a> to your project:</li> </ol> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet <span class="token function">add</span> package Marten.CommandLine</code></pre></div> <ol start="2"> <li>Register Marten commands by adding the following lines to your code:</li> </ol> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// add Marten</span> builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span>Host<span class="token punctuation">.</span><span class="token function">ApplyOaktonExtensions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span>Build <span class="token comment">// has to be the last line and no app.Run is needed</span> <span class="token keyword">return</span> <span class="token keyword">await</span> app<span class="token punctuation">.</span><span class="token function">RunOaktonCommands</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Marten internally is using <a href="https://jasperfx.github.io/oakton">Oakton</a> for command line processing.</p> <ol start="3"> <li>Then call from the command line:</li> </ol> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">dotnet run -- codegen <span class="token function">write</span></code></pre></div> <ol> <li>You can also use the <a href="https://martendb.io/configuration/optimized_artifact_workflow.html">Optimised Artifact Workflow</a>. Which will set dynamic code generation for the Development environment and setup code generation for the Production:</li> </ol> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// Add this line to Marten config</span> <span class="token punctuation">.</span><span class="token function">OptimizeArtifactWorkflow</span><span class="token punctuation">(</span>TypeLoadMode<span class="token punctuation">.</span>Static<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>If you configured everything correctly, your app is set up for Production usage.</p> <p><strong>Let’s include that in the Dockerfile configuration. We’ll use the optimised multistage build described in <a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">previous article</a>.</strong> We’ll try to make it reusable and generic, so you can apply that easier to your project. We’ll use <a href="https://docs.docker.com/build/guide/build-args/">Docker to build arguments</a> to have configurable inputs for:</p> <ul> <li>.NET version,</li> <li>Project file name,</li> <li>whether to run code generation or not.</li> </ul> <p><strong>Why would we not want to run always code generation for our Docker image?</strong> We should always do it for deployment, but for <a href="https://containers.dev/">Development Containers</a>, we might want to skip that to reduce the built time.</p> <p>Image definition could look as follows:</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token instruction"><span class="token keyword">ARG</span> dotnet_version=8.0</span> <span class="token comment">########################################</span> <span class="token comment"># First stage of multistage build</span> <span class="token comment">########################################</span> <span class="token comment"># Use Build image with label `builder</span> <span class="token comment">########################################</span> <span class="token instruction"><span class="token keyword">FROM</span> mcr.microsoft.com/dotnet/sdk:<span class="token variable">${dotnet_version}</span>-alpine <span class="token keyword">AS</span> builder</span> <span class="token comment"># Project file name, mandatory parameter, e.g. Helpdesk.Api</span> <span class="token instruction"><span class="token keyword">ARG</span> project_name</span> <span class="token instruction"><span class="token keyword">ARG</span> run_codegen=true</span> <span class="token comment"># Setup working directory for project</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token instruction"><span class="token keyword">COPY</span> ./<span class="token variable">${project_name}</span>.csproj ./</span> <span class="token comment"># Restore nuget packages</span> <span class="token instruction"><span class="token keyword">RUN</span> dotnet restore ./<span class="token variable">${project_name}</span>.csproj</span> <span class="token comment"># Copy project files</span> <span class="token instruction"><span class="token keyword">COPY</span> ./ ./</span> <span class="token comment"># Run code generation depending on the build argument</span> <span class="token instruction"><span class="token keyword">RUN</span> if [ <span class="token string">"${run_codegen}"</span> = true ] ; then dotnet run -- codegen write &amp; dotnet run -- codegen test; else echo <span class="token string">"skipping code generation"</span>; fi</span> <span class="token comment"># Build project with Release configuration</span> <span class="token comment"># and no restore, as we did it already</span> <span class="token instruction"><span class="token keyword">RUN</span> dotnet build -c Release --no-restore ./<span class="token variable">${project_name}</span>.csproj</span> <span class="token comment"># Publish project to output folder</span> <span class="token comment"># and no build, as we did it already</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app/</span> <span class="token instruction"><span class="token keyword">RUN</span> ls</span> <span class="token instruction"><span class="token keyword">RUN</span> dotnet publish -c Release --no-build -o out</span> <span class="token comment">########################################</span> <span class="token comment"># Second stage of multistage build</span> <span class="token comment">########################################</span> <span class="token comment"># Use other build image as the final one</span> <span class="token comment"># that won't have source codes</span> <span class="token comment">########################################</span> <span class="token instruction"><span class="token keyword">FROM</span> mcr.microsoft.com/dotnet/aspnet:<span class="token variable">${dotnet_version}</span>-alpine</span> <span class="token instruction"><span class="token keyword">ARG</span> project_name</span> <span class="token instruction"><span class="token keyword">ARG</span> dotnet_version=8.0</span> <span class="token comment"># Setup working directory for project</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token comment"># Copy published in previous stage binaries</span> <span class="token comment"># from the `builder` image</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">builder</span></span> /app/out .</span> <span class="token comment"># Set URL that App will be exposed</span> <span class="token instruction"><span class="token keyword">ENV</span> ASPNETCORE_URLS=<span class="token string">"http://*:5000"</span></span> <span class="token instruction"><span class="token keyword">ENV</span> PROJECT_DLL=<span class="token string">"${project_name}.dll"</span></span> <span class="token comment"># sets entry point command to automatically</span> <span class="token comment"># run application on `docker run`</span> <span class="token instruction"><span class="token keyword">ENTRYPOINT</span> dotnet <span class="token variable">$PROJECT_DLL</span></span></code></pre></div> <p>You’d need to put it in the runtime project location and call it container build as:</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build --build-arg <span class="token assign-left variable">project_name</span><span class="token operator">=</span>Helpdesk.Api <span class="token builtin class-name">.</span> -t helpdesk</code></pre></div> <p>Or with all parameters</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token function">docker</span> build --build-arg <span class="token assign-left variable">project_name</span><span class="token operator">=</span>Helpdesk.Api --build-arg <span class="token assign-left variable">codegen</span><span class="token operator">=</span>true --build-arg <span class="token assign-left variable">dotnet_version</span><span class="token operator">=</span><span class="token number">8.0</span> <span class="token builtin class-name">.</span> -t helpdesk</code></pre></div> <p><strong>Beside the configurable input arguments, the Docker definition does one specific thing: pre-building generated code.</strong> It has to go after project files are copied and dependencies, but <strong>before the build.</strong> Why? Because if we use static type load, the application will fail if no files are generated.</p> <p>This is the line responsible for doing the code generation, together with a dry run test.</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token instruction"><span class="token keyword">RUN</span> if [ <span class="token string">"run_codegen"</span> = true ] ; then dotnet run -- codegen write &amp; dotnet run -- codegen test; else echo <span class="token string">"skipping code generation"</span>; fi</span></code></pre></div> <p>You can see all the changes in the <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/218">Pull Request</a> and play with the <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/Helpdesk">full sample</a> in my repository.</p> <p>Once you build the image, you can also <a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">push it to Container Registry</a></p> <p>I’m considering adding an option in our command line to generate such Dockerfile; feel free to share your thoughts and ideas for improvements!</p> <p>Read also other articles around DevOps process:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Vertical Slices in practice]]>https://event-driven.io/en/vertical_slices_in_practice/https://event-driven.io/en/vertical_slices_in_practice/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/12b74e544624d5c5d201394a3bc22145/a331c/2023-07-09-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABOUlEQVQoz5XQT0vCYADHcV9ZQe/A1IIZCZmWHqzZ3LM/mFhLcW01Hxe0Z04xp6YFFnTICaKkTgidEh06dujYsXt09/D0Aj7w/f1cdk+5zsS0w/VJOsgks+OnVMcApuxtAWrSTDpKaM5v9zXeyoc0sPo9gFY82pF5lvL3t9ZcS3HtDyfm7dSHujsXwpZx1rkIIkxsSr42G1dz9FVi5/U4MCqCLgxrYAUPy74HQMoShDw54CMjRFt5TFxiqqL7MSt+fv1MyyfOkWeos118bIru+0t1MHkfm+KC9g4Ri5v9XKSbkEBZuaRXnHJqATb+h2+VzTvtpm6gKYy+ccQLYvAPY+qSu5pTGnphBiMzhhgiBnuzARqyB6XPa/C0Jx1osX0bURbcw8vW6ZbqL2QgEsiKQBY4ztYpK78E/wIoRXBZ3BdtrQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/12b74e544624d5c5d201394a3bc22145/a331c/2023-07-09-cover.png" srcset="/static/12b74e544624d5c5d201394a3bc22145/36ca5/2023-07-09-cover.png 200w, /static/12b74e544624d5c5d201394a3bc22145/a3397/2023-07-09-cover.png 400w, /static/12b74e544624d5c5d201394a3bc22145/a331c/2023-07-09-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I’m a preacher for the <a href="/en/cqrs_facts_and_myths_explained/">CQRS</a>, Vertical Slices, and Feature Folders.</strong> I won’t hide that, and I won’t even try. I believe that structuring code based on the business feature helps deliver business value, thanks to an increased focus on the domain and reduced cognitive load.</p> <p>I explained already in my articles why <a href="/en/generic_does_not_mean_simple/">Generic does not mean Simple</a> and <a href="/en/how_to_slice_the_codebase_effectively/">how to slice the codebase effectively</a>. I showed that in my talk:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/iY7LO289qnQ?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>Today, I’ll take a step further and give more practical guidance on simple but closer to the real-world case</strong>. The focus will be on the structural part, but I’ll add a sprinkle of Event Sourcing and use <a href="https://martendb.io/">Marten</a> to make it more real.</p> <p><strong>Let’s say we’d like to implement a Room Reservation module in the Hotel Management system.</strong> Reservation can be initiated either from the user through our UI and API call or from an external system like Booking.com. Our flow, for now, looks almost the same, and we don’t want to overcomplicate things.</p> <p>We could define the following events representing a business flow of the Reservation:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RoomReserved</span> <span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> ReservationId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> ExternalReservationId<span class="token punctuation">,</span> <span class="token class-name">RoomType</span> RoomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> From<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> To<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> GuestId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> NumberOfPeople<span class="token punctuation">,</span> <span class="token class-name">ReservationSource</span> Source<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> MadeAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RoomReservationConfirmed</span> <span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> ReservationId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ConfirmedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RoomReservationCancelled</span> <span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> ReservationId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> CancelledAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">RoomType</span> <span class="token punctuation">{</span> Single <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> Twin <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">,</span> King <span class="token operator">=</span> <span class="token number">3</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">ReservationSource</span> <span class="token punctuation">{</span> Api<span class="token punctuation">,</span> External <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">ReservationStatus</span> <span class="token punctuation">{</span> Pending<span class="token punctuation">,</span> Confirmed<span class="token punctuation">,</span> Cancelled <span class="token punctuation">}</span></code></pre></div> <p><strong>As you see, when reserving a room in the hotel, you’re not booking the specific room but the room type.</strong> That has intriguing consequences that we’ll discuss later on.</p> <p>To make our decisions, we need a Room Reservation entity representing the current state of our reservation process. It could look like that.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RoomReservation</span> <span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">,</span> <span class="token class-name">RoomType</span> RoomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> From<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> To<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> GuestId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> NumberOfPeople<span class="token punctuation">,</span> <span class="token class-name">ReservationSource</span> Source<span class="token punctuation">,</span> <span class="token class-name">ReservationStatus</span> Status<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> MadeAt<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> ConfirmedAt<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> CancelledAt <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">RoomReservation</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">RoomReserved</span> reserved<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> reserved<span class="token punctuation">.</span>ReservationId<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>RoomType<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>From<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>To<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>GuestId<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>NumberOfPeople<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>Source<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>Source <span class="token operator">==</span> ReservationSource<span class="token punctuation">.</span>External <span class="token punctuation">?</span> ReservationStatus<span class="token punctuation">.</span>Confirmed <span class="token punctuation">:</span> ReservationStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>MadeAt<span class="token punctuation">,</span> reserved<span class="token punctuation">.</span>Source <span class="token operator">==</span> ReservationSource<span class="token punctuation">.</span>External <span class="token punctuation">?</span> reserved<span class="token punctuation">.</span>MadeAt <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token keyword">null</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">RoomReservation</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">RoomReservationConfirmed</span> confirmed<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ReservationStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">,</span> ConfirmedAt <span class="token operator">=</span> confirmed<span class="token punctuation">.</span>ConfirmedAt <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">RoomReservation</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">RoomReservationCancelled</span> confirmed<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ReservationStatus<span class="token punctuation">.</span>Cancelled<span class="token punctuation">,</span> ConfirmedAt <span class="token operator">=</span> confirmed<span class="token punctuation">.</span>CancelledAt <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>In the <em>RoomReserved</em> event apply method, we already see that external reservation has a different flow than the API one. We assume that it’s already confirmed and paid once we get it. That’s different from our regular reservation. It’ll need to go through an additional flow and be explicitly confirmed after making payment etc.</p> <p>We could define <a href="/en/union_types_in_csharp/">union types</a> and different events for internal and external reservations, but let’s focus today on the structure rather than the <a href="/en/how_to_effectively_compose_your_business_logic/">modelling</a>.</p> <p><strong>As we have more stuff around the room reservation process, let’s create a dedicated folder <em>RoomReservations</em>.</strong> Inside it, we can define the <em>RoomReservation.cs</em> file and put the code we described above. It shapes our domain model. We’ll be working around it when defining our process. When we add a new event, we must update the entity, etc. It also forms documentation of our flow.</p> <p><strong>Let’s now implement the business logic for our room reservation</strong>. Let’s create a nested folder called <em>ReservingRoom</em>. It’ll encapsulate the reservation initiation process. We’ll also need a command and its handler. Let’s define the <em>ReserveRoom.cs</em> file inside the newly created folder.</p> <p>We’ll keep there both command definition and business logic for the operation. In most cases, when we’re changing command, we need to change logic. And when we change logic, we’d like to see the command definition. It’s about <a href="/en/stacking_the_bricks/">ergonomy and developer experience</a>.</p> <p>It could look like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ReserveRoom</span> <span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> ReservationId<span class="token punctuation">,</span> <span class="token class-name">RoomType</span> RoomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> From<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> To<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> GuestId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> NumberOfPeople<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> Now<span class="token punctuation">,</span> <span class="token class-name">ReservationSource</span> ReservationSource<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>DailyRoomTypeAvailability<span class="token punctuation">></span></span> DailyAvailability<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> ExternalId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">RoomReserved</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ReserveRoom</span> command<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> reservationSource <span class="token operator">=</span> command<span class="token punctuation">.</span>ReservationSource<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> dailyAvailability <span class="token operator">=</span> command<span class="token punctuation">.</span>DailyAvailability<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>reservationSource <span class="token operator">==</span> ReservationSource<span class="token punctuation">.</span>Api <span class="token operator">&amp;&amp;</span> dailyAvailability<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>a <span class="token operator">=></span> a<span class="token punctuation">.</span>AvailableRooms <span class="token operator">&lt;</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Not enough available rooms!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RoomReserved</span><span class="token punctuation">(</span> command<span class="token punctuation">.</span>ReservationId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>ExternalId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>RoomType<span class="token punctuation">,</span> command<span class="token punctuation">.</span>From<span class="token punctuation">,</span> command<span class="token punctuation">.</span>To<span class="token punctuation">,</span> command<span class="token punctuation">.</span>GuestId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>NumberOfPeople<span class="token punctuation">,</span> command<span class="token punctuation">.</span>ReservationSource<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Now <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ReserveRoom</span> <span class="token function">FromApi</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> id<span class="token punctuation">,</span> <span class="token class-name">RoomType</span> roomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> from<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> to<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> guestId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> numberOfPeople<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> now<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>DailyRoomTypeAvailability<span class="token punctuation">></span></span> dailyAvailability <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> id<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> roomType<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> from<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> to<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AssertGreaterOrEqualThan</span><span class="token punctuation">(</span>from<span class="token punctuation">)</span><span class="token punctuation">,</span> guestId<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> numberOfPeople<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> now<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ReservationSource<span class="token punctuation">.</span>Api<span class="token punctuation">,</span> dailyAvailability<span class="token punctuation">,</span> <span class="token keyword">null</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ReserveRoom</span> <span class="token function">FromExternal</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> externalId<span class="token punctuation">,</span> <span class="token class-name">RoomType</span> roomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> from<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> to<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> guestId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> numberOfPeople<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> now <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> id<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> roomType<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> from<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> to<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AssertGreaterOrEqualThan</span><span class="token punctuation">(</span>from<span class="token punctuation">)</span><span class="token punctuation">,</span> guestId<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> numberOfPeople<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> now<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ReservationSource<span class="token punctuation">.</span>External<span class="token punctuation">,</span> Array<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Empty</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>DailyRoomTypeAvailability<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> externalId<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s a pretty straightforward code. A command is an <a href="/en/notes_about_csharp_records_and_nullable_reference_types/">immutable record</a> we’re handling in a function. The command is created and <a href="/en/explicit_validation_in_csharp_just_got_simpler/">explicitly validated</a> as I’d like to trust my objects.</p> <p>Handler has a basic logic checking for reserving a room based on the room type daily availability data. I could pass it as a handler param, but I prefer to put it into the command, which makes logic predictable. That makes it seamless to test, as I can validate if I’m getting an expected result for the specific input data: event or exception. I could use the <a href="/en/union_types_in_csharp/">Result</a> type instead of throwing an exception, but that’s a matter of personal preference.</p> <p>I’m not doing availability validation, as it’s already a fact that someone reserved a room in an external system. Throwing an exception won’t change that fact. We need to embrace that and compensate for the overbooking (read more in <a href="/en/what_texting_ex_has_to_do_with_event_driven_design/">What texting your Ex has to do with Event-Driven Design?</a>).</p> <p><strong>Ok, but how to model different inputs of our process?</strong> Where to put the application code for them? Best in the same folder as the business logic but in separate files. Let’s add two of them to <em>RoomReservations.ReservingRoom</em> folder:</p> <ul> <li><em>ReserveRoomEndpoint.cs</em> - containing the API endpoint definition,</li> <li><em>BookingComRoomReservationMadeHandler.cs</em> - with the event handler for the Booking.com event.</li> </ul> <p><strong>Both will trigger the same workflow but with different inputs and application logic.</strong></p> <p>Let’s start with the API endpoint. Using .NET Minimal API it could look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ReserveRoomEndpoint</span> <span class="token punctuation">{</span> <span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token return-type class-name">IEndpointRouteBuilder</span> <span class="token function">UseReserveRoomEndpoint</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEndpointRouteBuilder</span> endpoints<span class="token punctuation">)</span> <span class="token punctuation">{</span> endpoints<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"api/reservations/"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">,</span> <span class="token class-name">ReserveRoomRequest</span> request<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>roomType<span class="token punctuation">,</span> from<span class="token punctuation">,</span> to<span class="token punctuation">,</span> guestId<span class="token punctuation">,</span> numberOfPeople<span class="token punctuation">)</span> <span class="token operator">=</span> request<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reservationId <span class="token operator">=</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> dailyAvailability <span class="token operator">=</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token function">GetRoomTypeAvailabilityForPeriod</span><span class="token punctuation">(</span><span class="token function">Of</span><span class="token punctuation">(</span>roomType<span class="token punctuation">,</span> from<span class="token punctuation">,</span> to<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> ReserveRoom<span class="token punctuation">.</span><span class="token function">FromApi</span><span class="token punctuation">(</span> reservationId<span class="token punctuation">,</span> roomType<span class="token punctuation">,</span> from<span class="token punctuation">,</span> to<span class="token punctuation">,</span> guestId<span class="token punctuation">,</span> numberOfPeople<span class="token punctuation">,</span> DateTimeOffset<span class="token punctuation">.</span>Now<span class="token punctuation">,</span> dailyAvailability <span class="token punctuation">)</span><span class="token punctuation">;</span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">StartStream</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RoomReservation<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>reservationId<span class="token punctuation">,</span> ReserveRoom<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Created</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/reservations/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">reservationId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> reservationId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> endpoints<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ReserveRoomRequest</span><span class="token punctuation">(</span> <span class="token class-name">RoomType</span> RoomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> From<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> To<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> GuestId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> NumberOfPeople <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It:</p> <ul> <li>does mapping from the API request into a command using <em>ReserveRoom.FromApi</em>,</li> <li>generates reservation id,</li> <li>retrieves availability information</li> <li>calls business logic and stores the result room reserved event in the Marten event store if it succeeds. If not, it returns an error status.</li> </ul> <p><strong>How event handler will look like?</strong> There, you have it:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">BookingComRoomReservationMade</span> <span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> ReservationId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> RoomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> Start<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> End<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> GuestProfileId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> GuestsCounts<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> MadeAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookingComRoomReservationMadeHandler</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span>BookingComRoomReservationMade<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">BookingComRoomReservationMadeHandler</span><span class="token punctuation">(</span><span class="token class-name">IDocumentSession</span> session<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>session <span class="token operator">=</span> session<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">BookingComRoomReservationMade</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>bookingComReservationId<span class="token punctuation">,</span> roomTypeText<span class="token punctuation">,</span> from<span class="token punctuation">,</span> to<span class="token punctuation">,</span> bookingComGuestId<span class="token punctuation">,</span> numberOfPeople<span class="token punctuation">,</span> madeAt<span class="token punctuation">)</span> <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reservationId <span class="token operator">=</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> guestId <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">Query</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">GetGuestIdByExternalId</span><span class="token punctuation">(</span><span class="token function">FromPrefix</span><span class="token punctuation">(</span><span class="token string">"BCOM"</span><span class="token punctuation">,</span> bookingComGuestId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> roomType <span class="token operator">=</span> Enum<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Parse</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RoomType<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>roomTypeText<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> ReserveRoom<span class="token punctuation">.</span><span class="token function">FromExternal</span><span class="token punctuation">(</span> reservationId<span class="token punctuation">,</span> bookingComReservationId<span class="token punctuation">,</span> roomType<span class="token punctuation">,</span> from<span class="token punctuation">,</span> to<span class="token punctuation">,</span> guestId<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> numberOfPeople<span class="token punctuation">,</span> madeAt <span class="token punctuation">)</span><span class="token punctuation">;</span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">StartStream</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RoomReservation<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>reservationId<span class="token punctuation">,</span> ReserveRoom<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>It’s similar to the previous one but differs in the application logic.</strong> We’re not loading availability information, as we won’t validate it for external reservations. We’re also calling mapping to get (and potentially create) guest id based on the Booking.com identifier. We’re also doing other mappings to show that we may use those handlers as <a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/anti-corruption-layer">Anti-Corruption Layer</a>.</p> <p><strong>That creates a basic structure for our process:</strong></p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">📁 ReservationModule 📁 RoomReservations 📁 ReservingRoom 📄 BookingComRoomReservationMadeHandler.cs 📄 ReserveRoom.cs 📄 ReserveRoomEndpoint.cs 📄 Reservation.cs</code></pre></div> <p><strong>What to do with dependencies for handling room type availability, overbooking and guest id mapping?</strong> To get an answer for your case, you should ask your business and investigate the relations between processes. Still, for this example, we need to assume something. Here’s what I propose.</p> <p>Room type availability and overbooking will be only used during the reservation process and calculated based on the information from it, so let’s keep it as part of it.</p> <p>Guest id mapping will obviously need to be a different module, as it will have other flows around it.</p> <p><strong>As I mentioned, we’d like to keep things simple and don’t overengineer. Yet, we’d like to have the option to evolve and, e.g. in future, elevate our subprocesses into autonomous modules.</strong></p> <p><strong>Let’s start by defining <em>GettingRoomTypeAvailability</em> as a subfolder of <em>RoomReservations.ReservingRoom</em> and adding there <em>DailyRoomTypeAvailability.cs</em>.</strong> It’ll contain information about room type availability. It’ll be a read model built from the reservation events. Using <a href="/en/projections_in_marten_explained/">Marten projections</a> it could look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">DailyRoomTypeAvailability</span> <span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> Date<span class="token punctuation">,</span> <span class="token class-name">RoomType</span> RoomType<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> ReservedRooms<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Capacity<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> AllowedOverbooking <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> CapacityWithOverbooking <span class="token operator">=></span> Capacity <span class="token operator">+</span> AllowedOverbooking<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> AvailableRooms <span class="token operator">=></span> CapacityWithOverbooking <span class="token operator">-</span> ReservedRooms<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Overbooked <span class="token operator">=></span> ReservedRooms <span class="token operator">-</span> Capacity<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> OverbookedOverTheLimit <span class="token operator">=></span> ReservedRooms <span class="token operator">-</span> CapacityWithOverbooking<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DailyRoomTypeAvailabilityProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">MultiStreamProjection<span class="token punctuation">&lt;</span>DailyRoomTypeAvailability<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">DailyRoomTypeAvailabilityProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token generic-method"><span class="token function">Identities</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RoomReserved<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> Enumerable<span class="token punctuation">.</span><span class="token function">Range</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>To<span class="token punctuation">.</span>DayNumber <span class="token operator">-</span> e<span class="token punctuation">.</span>From<span class="token punctuation">.</span>DayNumber<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>offset <span class="token operator">=></span> <span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">e<span class="token punctuation">.</span>RoomType</span><span class="token punctuation">}</span></span><span class="token string">_</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">e<span class="token punctuation">.</span>To<span class="token punctuation">.</span><span class="token function">AddDays</span><span class="token punctuation">(</span>offset<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">DailyRoomTypeAvailability</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">DailyRoomTypeAvailability</span> availability<span class="token punctuation">,</span> <span class="token class-name">RoomReserved</span> reserved<span class="token punctuation">)</span> <span class="token operator">=></span> availability <span class="token keyword">with</span> <span class="token punctuation">{</span> ReservedRooms <span class="token operator">=</span> availability<span class="token punctuation">.</span>ReservedRooms <span class="token operator">+</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The only complex part is taking the room type and date range from the <em>RoomReserved</em> event and matching that with the read model rows. We can do that by defining the id format as <em>$“{RoomType}_{Date}”</em>.</p> <p><strong>Now, let’s define the query for room type availability within the particular period:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">GetRoomTypeAvailabilityForPeriod</span><span class="token punctuation">(</span> <span class="token class-name">RoomType</span> RoomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> From<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> To <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">GetRoomTypeAvailabilityForPeriod</span> <span class="token function">Of</span><span class="token punctuation">(</span> <span class="token class-name">RoomType</span> roomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> from<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> to <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> roomType<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> from<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> to<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AssertGreaterOrEqualThan</span><span class="token punctuation">(</span>from<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">GetRoomTypeAvailabilityForPeriodHandler</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>IReadOnlyList<span class="token punctuation">&lt;</span>DailyRoomTypeAvailability<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">GetRoomTypeAvailabilityForPeriod</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IQuerySession</span> session<span class="token punctuation">,</span> <span class="token class-name">GetRoomTypeAvailabilityForPeriod</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> session<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Query</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>DailyRoomTypeAvailability<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>day <span class="token operator">=></span> day<span class="token punctuation">.</span>RoomType <span class="token operator">==</span> query<span class="token punctuation">.</span>RoomType <span class="token operator">&amp;&amp;</span> day<span class="token punctuation">.</span>Date <span class="token operator">>=</span> query<span class="token punctuation">.</span>From <span class="token operator">&amp;&amp;</span> day<span class="token punctuation">.</span>Date <span class="token operator">&lt;=</span> query<span class="token punctuation">.</span>To<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">token</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Nothing special here besides the fact that we don’t use any query bus, marker interfaces etc. It’s inside the module, so it is unnecessary to overcomplicate it.</p> <p><a href="/en/publishing_read_model_changes_from_marten/">Marten gives the option to listen for changes in the read model</a>. We can use it to detect if we have overbooking. That’s, again, a shortcut to avoid reinventing the wheel if we’re inside the same module.</p> <p>Such usage also gives the possibility for on-point performance improvements. <a href="https://jeremydmiller.com/2023/07/12/compiled-queries-with-marten/">Jeremy did a follow-up explaining how Marten’s compiled queries could help in that</a>.</p> <p><strong>Let’s create <em>OverbookingDetection</em> subfolder inside <em>RoomReservations.ReservingRoom</em> and put there a <em>DailyOverbookingDetector.cs</em> file and put there logic for detecting overbooking:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">DailyOverbookingDetected</span> <span class="token punctuation">(</span> <span class="token class-name">RoomType</span> RoomType<span class="token punctuation">,</span> <span class="token class-name">DateOnly</span> Date<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> OverBookedCount<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> OverBookedOverTheLimitCount <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DailyOverbookingDetector</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IChangeListener</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IEventBus</span> eventBus<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">DailyOverbookingDetector</span><span class="token punctuation">(</span><span class="token class-name">IEventBus</span> eventBus<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventBus <span class="token operator">=</span> eventBus<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">AfterCommitAsync</span><span class="token punctuation">(</span><span class="token class-name">IDocumentSession</span> session<span class="token punctuation">,</span> <span class="token class-name">IChangeSet</span> commit<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> token<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> events <span class="token operator">=</span> commit<span class="token punctuation">.</span>Inserted<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">OfType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>DailyRoomTypeAvailability<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>commit<span class="token punctuation">.</span>Updated<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">OfType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>DailyRoomTypeAvailability<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>availability <span class="token operator">=></span> availability<span class="token punctuation">.</span>Overbooked <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>availability <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DailyOverbookingDetected</span><span class="token punctuation">(</span> availability<span class="token punctuation">.</span>RoomType<span class="token punctuation">,</span> availability<span class="token punctuation">.</span>Date<span class="token punctuation">,</span> availability<span class="token punctuation">.</span>Overbooked<span class="token punctuation">,</span> availability<span class="token punctuation">.</span>OverbookedOverTheLimit <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> eventBus<span class="token punctuation">.</span><span class="token function">Publish</span><span class="token punctuation">(</span>EventEnvelope<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">,</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re filtering the pending changes containing updated read models and getting overbooked ones. Based on them, we create and publish events to the event bus.</p> <p><strong>What to do with them it’s, of course, a matter of our business requirements and tech stack.</strong> We could trigger compensating business logic based on them, store them as events, or republish them to some messaging system and trigger workflows elsewhere.</p> <p><strong>Last but not least, let’s discuss the external module integration.</strong> We could create a <em>Guests</em> subfolder inside the root of our module and put there <em>Guest.cs</em> with a basic interpretation of the data from an external system. For us, it’ll be enough to keep it simple as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">GuestExternalId</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> Value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">GuestExternalId</span> <span class="token function">FromPrefix</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> prefix<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> externalId<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">prefix<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">externalId<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">GuestId</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> Value<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then let’s define the <em>GettingGuestByExternalId</em> subfolder and put there the <em>GetGuestByExternalId.cs</em> file.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">GetGuestIdByExternalId</span> <span class="token punctuation">(</span> <span class="token class-name">GuestExternalId</span> ExternalId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>GuestId<span class="token punctuation">></span></span> <span class="token function">Query</span><span class="token punctuation">(</span><span class="token class-name">GetGuestIdByExternalId</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Here, you'd probably call some external module</span> <span class="token comment">// Or even orchestrate creation if it doesn't exist already</span> <span class="token comment">// But I'm just doing dummy mapping</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ValueTask<span class="token punctuation">&lt;</span>GuestId<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">GuestId</span><span class="token punctuation">(</span>query<span class="token punctuation">.</span>ExternalId<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Of course, it’s a dummy implementation, but again, it lets you keep things explicit and self-explanatory. We could either call it explicitly as we did in the Event Handler.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">Reservations<span class="token punctuation">.</span>Guests<span class="token punctuation">.</span>GettingGuestByExternalId<span class="token punctuation">.</span>GetGuestIdByExternalId</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookingComRoomReservationMadeHandler</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span>BookingComRoomReservationMade<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">BookingComRoomReservationMadeHandler</span><span class="token punctuation">(</span><span class="token class-name">IDocumentSession</span> session<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>session <span class="token operator">=</span> session<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">BookingComRoomReservationMade</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>bookingComReservationId<span class="token punctuation">,</span> roomTypeText<span class="token punctuation">,</span> from<span class="token punctuation">,</span> to<span class="token punctuation">,</span> bookingComGuestId<span class="token punctuation">,</span> numberOfPeople<span class="token punctuation">,</span> madeAt<span class="token punctuation">)</span> <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> guestId <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">Query</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">GetGuestIdByExternalId</span><span class="token punctuation">(</span><span class="token function">FromPrefix</span><span class="token punctuation">(</span><span class="token string">"BCOM"</span><span class="token punctuation">,</span> bookingComGuestId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>That makes usage straightforward but also coupled. As we’re running a potential code from another module, it may be worth adding abstraction. If we’d like to keep the boundaries explicit, to have e.g. better test isolation, we could inject query as a function:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">delegate</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>GuestId<span class="token punctuation">></span></span> <span class="token function">GetGuestId</span><span class="token punctuation">(</span><span class="token class-name">GetGuestIdByExternalId</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BookingComRoomReservationMadeHandler</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span>BookingComRoomReservationMade<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">GetGuestId</span> getGuestId<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">BookingComRoomReservationMadeHandler</span><span class="token punctuation">(</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">,</span> <span class="token class-name">GetGuestId</span> getGuestId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>session <span class="token operator">=</span> session<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>getGuestId <span class="token operator">=</span> getGuestId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">BookingComRoomReservationMade</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>bookingComReservationId<span class="token punctuation">,</span> roomTypeText<span class="token punctuation">,</span> from<span class="token punctuation">,</span> to<span class="token punctuation">,</span> bookingComGuestId<span class="token punctuation">,</span> numberOfPeople<span class="token punctuation">,</span> madeAt<span class="token punctuation">)</span> <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reservationId <span class="token operator">=</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> guestId <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getGuestId</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">GetGuestIdByExternalId</span><span class="token punctuation">(</span><span class="token function">FromPrefix</span><span class="token punctuation">(</span><span class="token string">"BCOM"</span><span class="token punctuation">,</span> bookingComGuestId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We could also use the interface like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IQuery<span class="token punctuation">&lt;</span>TQuery<span class="token punctuation">,</span> <span class="token keyword">out</span> TResponse<span class="token punctuation">></span></span> <span class="token keyword">where</span> <span class="token class-name">T</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token punctuation">{</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>TResponse<span class="token punctuation">></span></span> <span class="token function">Query</span><span class="token punctuation">(</span><span class="token class-name">TQuery</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And inject it, but I think it just adds more ceremony. Still, it’s your call. The most important is to define our boundaries and draw them explicitly where needed to not end up with a big ball of mud.</p> <p>The final code structure looks as follows:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">📁 ReservationModule 📁 Guests 📁 GettingGuestByExternalId 📄 GettingGuestByExternalId.cs 📄 Guest.cs 📁 RoomReservations 📁 GettingRoomTypeAvailability 📄 DailyRoomTypeAvailability.cs 📄 GetRoomTypeAvailabilityForPeriod.cs 📁 OverbookingDetection 📄 DailyOverbookingDetector.cs 📁 ReservingRoom 📄 BookingComRoomReservationMadeHandler.cs 📄 ReserveRoom.cs 📄 ReserveRoomEndpoint.cs 📄 Reservation.cs</code></pre></div> <p>See the full code in <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/217">my sample repo</a>.</p> <p><strong>Is it the best naming and folder structure you could achieve?</strong> Maybe, but probably not. And that’s fine. We should embrace that our initial design will be wrong. Knowing that we can focus on making our code easier to reshuffle, target <a href="/en/removability_over_maintainability/">Removability over maintainability</a>.</p> <p>Having code sliced by business domain, straightforward and composed instead of generalised, allows us to reshuffle and correct our past decision. We can evolve and introduce more abstractions if we need them and when we need them.</p> <p>I hope this article will bring you vertical slices closer to home. Still, I encourage you to play with it and see what you come up with. You can take this example as the starting point and try to improve it.</p> <p><strong>In the end, it’s all about having more options in our Designer Toolbox!</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Follow the money to get a better design]]>https://event-driven.io/en/follow_the_money/https://event-driven.io/en/follow_the_money/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/646623cfa35f1157b69e17c27e67a91f/a331c/2023-07-02-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AGhteGduenR6hnN6hYKHknpmXwwCAAMEBgIBAgMCAQ8IBxQFAGFVVYSLn2tyfHF7h5iYn2JWUltgbWtufABeV2djWWdwZnl+eJCcjZdTPjMJAAAjDQQ5GQxEIRJbNSksFw8uHxp3cHlnYWl8d4OXj5VvX1h9eIWHg5EAhn6jiYKljYSimpSzq5usWT8vKA4DRh4LXi4Zcj8qgE89WjYnKh0ZgHV9aVxkb2Jti36Dn5WkgnulfnSOAJ2Wr6CewZeSuZWLnrGipmxUR2lRRjAZD0ogEWE1JGM9L4NWRFw7MoBoZFUyLFQxLYl7gKCWpIFzf62nsQCmmJt8b3OGfIibkZ6Ldm9UPTJ5YFQXAgA5FAxsOitcNSqedGOFTDqFXFRpVU5OLiVsYGJ2ammCdnnAu7wAdGdpUUNHZVhYd2VdZjgmMhAIKQ4INxECTiQbnWFTmWRVpXZojVZGh2ZmZ09OVjo0eW5vp5+fhnt8kIWAAB0SCxgOChUNDGNEN2E2JSYVECgHA0UTBEcVCY1TSJZgUpVbRp+Cgr3I4XhmbCsAAIJxc6GXmaGYpbOprAAbDwUeFAtxbGyZe3BMLyIjEQ1VIxlBDAA7EAVZMyp5SDmKU0C7usTK2fCdmamCeoKdmqWShIKSgYSun5sAKBwVlY6MlYF7jWtcd049WSseXDAlYzAdUBgQMAMBVyYalGVWzNPktrXFqaez2/f/yt/7fXV4UjYdhXBgAJ+Vma6fomxMPpl7bJFtXZpkUnBEOE8lHFYcEVAgGHA4KIhiWcLG2amks7e3ybzF3bfA2pKas1VEOlxKOwCKd3drUkt6VESafW5+WkqndWWOZ1xFJyM1BQBXIhZjMB+Eb2/Cxtmnn660scWlobOusMfN4v+SkZpiTkb1mgO/JiDIKgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/646623cfa35f1157b69e17c27e67a91f/a331c/2023-07-02-cover.png" srcset="/static/646623cfa35f1157b69e17c27e67a91f/36ca5/2023-07-02-cover.png 200w, /static/646623cfa35f1157b69e17c27e67a91f/a3397/2023-07-02-cover.png 400w, /static/646623cfa35f1157b69e17c27e67a91f/a331c/2023-07-02-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I noticed that we, developers, struggle to follow the money.</strong> And that’s impacting our design in the wrong way.</p> <p>Of course, we’re pretty good at getting high salaries, but I’m not speaking about that.</p> <p><strong>I’m speaking about understanding who’s the client and who’s the user of our system. Why is it important?</strong></p> <p>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?</p> <p>Let’s say that we’re building a SaaS platform like Shopify:</p> <ul> <li><strong>Who’s our client?</strong> A shop that’ll be paying for our subscriptions.</li> <li><strong>Who’ll be using our platform the most?</strong> People that’ll be buying goods in online shops. <strong>Will they be paying us?</strong> No, our clients.</li> </ul> <p><strong>Who should we optimise for?</strong> Our clients, so subscription payers. Should we include other users’ needs? Of course, but to the degree of balancing between making our clients happy and cost justification. So best if there’s a direct relationship between making users and clients happy.</p> <p>That may not sound like a nice thing to do. But we cannot make everyone happy. We should embrace that. That will help us to set up the priority. And that’s a basis for tradeoff analysis.</p> <p><strong>We should ask ourselves questions when discussing our features:</strong></p> <ul> <li>when will our client be sad?</li> <li>when will our user be sad?</li> <li>will the fact that our user is sad make our client sad eventually?</li> <li>will that be more disappointment, sadness or despair?</li> </ul> <p>Of course, talk to both clients and users. But remember also to follow the money.</p> <p>My intention is not to suggest ignoring user needs. I fully agree that it’s one of the things that is a must-have. Yet, if we’re too focused on our users, we may lose the end goal and who’ll be paying us in the end. I’m not saying we should be greedy, but understand why we are building our system and what’s a must-have for our product to survive.</p> <p><strong>And too often, clients’ needs are different from users’ needs.</strong> For instance, Google wouldn’t sell ads if there were not enough users using search. Still, they could have a massive number of users with no business model and go bankrupt. It is, of course, a reinforcement loop, but more than having users is needed to have paying clients. In the end, client satisfaction will make or break our success story.</p> <p>So, in other words, I’m voting for a good enough evolutionary approach, and without understanding the end goal (so <em>“where the money comes”</em>), we won’t be able to achieve that and make good tradeoffs.</p> <p>Of course, the basis is that we’re not trying to do evil.</p> <p>My suggestion to <em>follow money</em> is about more than just money. It’s about finding the essence of what makes our system go on. Who is the decision maker and what’s our business model, and how to fulfil it best?</p> <p><strong>Counterintuitively this is a decent exercise to start working on empathy.</strong> We’re starting to see businesses and people behind our system. That can help to design a proper resiliency without over-engineering.</p> <p>Following the money is also a decent technique to <a href="/en/the_risk_of_ignoring_risks/">find the risks</a> and crunch our initial design. If we were more focused on asking and listening to our domain experts about their needs and why we are building our tool, our design would also be more sound. The solution we’re using could potentially be more boring regarding the tech stack. But boring tech wins.</p> <p>Interestingly, in the emerging impact of tools like ChatGPT, and GitHub Copilot, <strong>empathy can be a skill that can be a difference-maker for you on the job market.</strong> Those tools will reduce the need for people who <em>“just want to code”</em>. Their job market value will lower. They will still be needed, but only if they’re good in the tech niche.</p> <p><strong>So who’s your client, and who’s your user?</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Read also more on how <a href="/en/a_few_words_on_communication/">A few words on communication</a> and the <a href="/en/bring_me_problems_not_solutions/">Bring me problems, not solutions!</a>.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Removability over Maintainability]]>https://event-driven.io/en/removability_over_maintainability/https://event-driven.io/en/removability_over_maintainability/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/7b9aa72ad5c914570c81e3d5bae8f7c0/a331c/2023-06-23-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAAChUlEQVQoz0XM3U9ScRgH8PMH9cJKC84JjsBBQEFBBA4v6gmEBCMQBHWi5EQltYumztebrljW1tpceeGmbuVSzNVsc4hNbb6Gm2+gAZ7f09CLnn32vfl+92CALgGuob95cCPLogyCHOSPBZT5Lz++AEgBnGEIThCcApwjSCFIAToHSF+k9ydfD3SFQxPjQzu/45D/ks63eecAJwDHAEcYe3WI2EPE/mHZJMsmEZsEOJueHBkKMGa1TKWQBP2One0f7FUSsUeITWYz++jqAOUO2OwuBrk9yF7L7aHcHsDR+ur8QFfg3WB7U43KZTM6mcqpNyOADlF2F2V2T48TmfR2JrV1mdrEVlbmf65+Xo8vJuKLG+ux5djMxHDf2+jYaLe/3a59GXo63Ov9FH31fWHq18ZyIv51be3r9tbK1ua3jcQSZnPaqy1mptbcHHD6vfZAwDk792Fu9n2bi7GpqZCX6QnWjYbdgz2BjkhHazjU2Rfp7o+E+1+0d4cxbZmwVEaaVGK6Qmql5QGPdeHLx9jidJuLYcqEjQ66xaELOug2f329z+dsaHB63A6Px/7M7fJ5MVqnpo16c5WJNpnUOoPeaIxGx5ZiM+UlEuIBp0QukMoEOoVIo1FrzYyhxmKtcxiqGV+ju9FtxWTlWrlaJ1Npi5UaqkQlEMsIktLSBhwncG6hlCIpEV9boYiEW5pbfBZbbZlGLy1VBv323iYLJpIpRVLlTZKSUoFYxuOLOIU4pxC/xXl45x6XU0AQpLhST1doVDiPd/vufYVS3trwePx5HcYTUFy+CCcpnJQQRZJHwmKiSEKQFJcvJEhRkViC80mcL8QFIk4BjhMCx5Oqof7m0V7/QKf7HwMfgb1hhGpEAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/7b9aa72ad5c914570c81e3d5bae8f7c0/a331c/2023-06-23-cover.png" srcset="/static/7b9aa72ad5c914570c81e3d5bae8f7c0/36ca5/2023-06-23-cover.png 200w, /static/7b9aa72ad5c914570c81e3d5bae8f7c0/a3397/2023-06-23-cover.png 400w, /static/7b9aa72ad5c914570c81e3d5bae8f7c0/a331c/2023-06-23-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <blockquote> <p>A diverse system with multiple pathways and redundancies is more stable and less vulnerable to external shock than a uniform system with little diversity.</p> </blockquote> <p>A single line from <a href="https://en.wikipedia.org/wiki/Donella_Meadows">Donella Meadows</a> has so many dimensions. Rereading it yesterday made me think about our legacy software systems.</p> <p>We should know to <a href="/en/chesterton_fence_and_software_architecture/">never underestimate the value of working software</a>, but why does it work even though it seems that no one should use it?</p> <p>The architecture looks obsolete, and the code has more <em><a href="https://en.wikipedia.org/wiki/Code_smell">code smells</a></em> than the properly written places. We’re afraid to make a change, but somehow we manage to patch it again. Our deployment doesn’t look like a slam dunk but more like a <a href="https://www.youtube.com/watch?v=1_uuArb_bho">euro step</a>.</p> <p><strong>Yet, somehow system still stands, and users are using it. Why?</strong></p> <p>Gerald Weinberg said:</p> <blockquote> <p>“Things are the way they are because they got that way”.</p> </blockquote> <p>The bad design may be both the weakness and power of the system. Weakness reveals in the inertia of changes and the power in the ability to keep it still going.</p> <p>We for sure heard the comment <em>just add one more IF</em>. That’s tempting, right? It can be a quick win to solve the issue, yet, it may also be a quick loss, making it harder to investigate the root cause. But hey! Maybe we won’t need to be doing but our accessor?</p> <p>That sounds bad, of course, but it’s undeniable that multiple people touching the same codebase are creating a code that no single one couldn’t come up with. That may end up in a sticky amalgamate. But it can also end up with diverse defensive programming guarding us against all the various weird edge cases we faced during our system lifetime.</p> <p><strong>That’s quite similar to <a href="https://www.youtube.com/watch?v=MZytZW_k-9Y">Barry O’Reilly’s Residuality Theory</a>.</strong> What we see in our code are residuals. The code and design pieces somehow survived all the years of being tackled by the stressors from the outside world and our internal organisation changes. They adapted.</p> <p><strong>In our industry, <a href="https://en.wikipedia.org/wiki/Maintainability">maintainability</a> is pictured as an ultimate goal, but should it be like that?</strong> Let me tell you a short story.</p> <p><a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">I’m a huge proponent of recording architecture decisions.</a> In one of my last projects, we had a sophisticated decision-making process. We wanted to make stuff right, have a log of our decisions and be fully transparent. While working on a new design or updating the existing one, each team should provide an <a href="https://en.wikipedia.org/wiki/Request_for_Comments">RFC-like</a> document and go through the <em>design review</em> process.</p> <p><strong>The Design Review process required internal approval from the team and then at least two more from people from the other teams.</strong> The intention was to have an immutable log for design changes, increase transparency and get cross-team collaboration and knowledge sharing. Also, to increase the diversity of views. Sounds right?</p> <p>That description sounds simple, but of course, we faced issues in applying that. They’re a story on their own, but we eventually got an overall sound quality of the design decisions. Such designs would end up in maintainable software.</p> <p><strong>Still, looking back, I’m unsure if having such a process was the correct decision for us. Why?</strong></p> <p>We wanted to build a new cloud version of the on-premise legacy system. We were not doing lift’n’shift; we wanted to build a new product based on the old one. That’s a challenge, as you want to take good experience from the old one and avoid repeating past mistakes. And there’s a tricky part in that.</p> <p>If you’re too focused on past mistakes, you may increase the likelihood of the new one. Or you may be so defensive that you’ll limit yourself and not go fast enough.</p> <p><strong>When you’re rewriting your product, you’re not rewriting it; you’re creating a new one.</strong> You should not assume that having your current knowledge and user base, you will repeat the good steps and avoid mishaps. We may fall into <a href="https://verraes.net/2021/05/its-just-like-heuristic/">the “it’s just like…” fallacy</a>. If your past system survived years, you should not assume that the new one will get to that point.</p> <p><strong>Your system may never reach the phase where maintainability is essential.</strong></p> <p>I do not like to move fast and break things. Our design review process was a good one, but not for us—a good one for an organisation of different maturity and context. In our case, we should think first about what we want to achieve and where we need to go to get to the place where our new business model is sustainable.</p> <p>Maybe it’d be better just to let teams do what they wanted. Make them work closer with domain experts, iterate fast and get a feedback loop. They should still document their decisions and let them ask for help from other teams when they need to. <a href="/en/on_the_importance_of_shaping_the_boundaries_in_team_management/">Shape the boundaries</a>, and allow them to operate within them. Of course, that sounds like chaos, but it doesn’t have to be.</p> <p>The critical thing here is to make teams accountable for their decisions. Let them fail and recover but face the consequences of bad choices. As long as we have a log of decision-making and constantly evaluating them, we can learn from them and survive.</p> <p><strong>Getting back to the diversity mentioned by Donella Meadows.</strong> We intended to increase the diversity of the decision-making, but by forcing all teams to fit the same process, we achieved the opposite. By that, we decreased the chance of our system to survive. Diversity is critical, giving us more options to evolve and pivot. <a href="https://www.youtube.com/watch?v=yV97QwC5gnE">Aaron Stannard called it optionality</a>, and that’s a good name; we’re increasing the number of options we have.</p> <p><strong>What if we let teams do what they wanted, creating an unmaintainable mess but reaching adoption?</strong> Then that’d be a decent problem to have. Jokes aside, how would I deal with that?</p> <p><strong>I think that we should optimise not for maintainability but <em>removability</em>.</strong> If our system is built in a way that we can relatively easy to remove pieces from it, then we can drop bad ideas and move with new ones. Also, by accident, we’re getting a system that’s easier to maintain. <a href="https://www.youtube.com/watch?v=Ed94CfxgsCA">Greg Young said</a>:</p> <blockquote> <p>One of beautiful things about deleting code its allows you to change your mind</p> </blockquote> <p><a href="/en/cqrs_facts_and_myths_explained/">That’s why I like CQRS.</a> By introducing business-focused vertical slices, we’re allowing them to be diverse and easier to remove. Each slice can represent a different feature and be implemented differently (if needed). That increases the likelihood of surviving unpredictable scenarios and pivoting our product and design decisions. Of course, CQRS won’t solve it all. I’m using it as an example solution for a technical approach.</p> <p><strong>The most important is to ensure that our delivery process matches our product phase and organisation structure.</strong> <a href="https://en.wikipedia.org/wiki/Conway%27s_law">Conway’s law</a>, of course. We also should embrace that our business will evolve, the industry will change, and we need to continuously adapt our organisation and approach to changing reality.</p> <p>Uber started with the attitude to never refactor but add new versions. Then they <a href="https://www.uber.com/en-PL/blog/microservice-architecture/">pivoted to domain-oriented architecture</a>. Amazon Prime started with a serverless approach, then they <a href="https://www.primevideotech.com/video-streaming/scaling-up-the-prime-video-audio-video-monitoring-service-and-reducing-costs-by-90">centrailised part of their system into micro-services</a>. And those are good examples of the evolutionary processes.</p> <p><strong>We’re making the most significant decisions when we’re dumbest.</strong> We don’t know our domain, and we don’t know the tech stack, yet we’re trying to guess the best option and present it as wise architecture decisions. We should embrace that we’re not that smart and help ourselves to make mistakes and learn from them. The model way is not always the best approach for our current situation.</p> <p><strong>We should focus on survival and creating the design that enables that.</strong> To achieve that, we must start simple and align our process with our current state of teams, organisation maturity, and knowledge about the product. Then try our system and our decisions in the wild to get feedback and see what’s still standing. By that, we’re increasing our knowledge about the client’s needs and whether our system can fulfil them.</p> <p>Such stimulation can help us increase diversity and build a system verified by real-world use cases. We won’t have the same design we expected, but we’ll have residuals that are much stronger than we’d come up with initially.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Read also more on how <a href="/en/why_are_we_afraid_of_our_decisions/">why are we afraid of our decisions</a> and the <a href="/en/the_risk_of_ignoring_risks/">risk of ignoring risks</a>.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Recap of Event Sourcing Live 2023]]>https://event-driven.io/en/event_sourcing_live_2023/https://event-driven.io/en/event_sourcing_live_2023/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/78403dc7d73ea0c59dbef7f6dab639ef/f2f5e/2023-06-15-08.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAMEBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHjWVKxrB//xAAaEAEBAAIDAAAAAAAAAAAAAAACAQADBBES/9oACAEBAAEFAgK3s461Hzg6i2Kow25//8QAFhEAAwAAAAAAAAAAAAAAAAAAAAES/9oACAEDAQE/AZZLP//EABcRAAMBAAAAAAAAAAAAAAAAAAABERL/2gAIAQIBAT8BqNI//8QAHRAAAQQCAwAAAAAAAAAAAAAAEQABAhASMSFBUf/aAAgBAQAGPwLFtrKQo8npCTvv2v/EABoQAQADAQEBAAAAAAAAAAAAAAEAESExUZH/2gAIAQEAAT8hE1WZygusYYG+z6UDFfkBtDMTMn//2gAMAwEAAgADAAAAEHTP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxAv/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAFBYf/aAAgBAgEBPxDYaqf/xAAcEAEBAAICAwAAAAAAAAAAAAABEQAhMUFhkfD/2gAIAQEAAT8QR+kCurl6OQ2byjARSvXrAYvEpm3zpMMNkI2/OSMT5Z//2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/78403dc7d73ea0c59dbef7f6dab639ef/c60e9/2023-06-15-08.jpg" srcset="/static/78403dc7d73ea0c59dbef7f6dab639ef/37402/2023-06-15-08.jpg 200w, /static/78403dc7d73ea0c59dbef7f6dab639ef/4cda9/2023-06-15-08.jpg 400w, /static/78403dc7d73ea0c59dbef7f6dab639ef/c60e9/2023-06-15-08.jpg 800w, /static/78403dc7d73ea0c59dbef7f6dab639ef/6c738/2023-06-15-08.jpg 1200w, /static/78403dc7d73ea0c59dbef7f6dab639ef/f2f5e/2023-06-15-08.jpg 1476w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://www.infoq.com/articles/architecture-trends-2023/">InfoQ claims</a> that Event Sourcing is in the <em>late majority</em> adoption phase.</strong> That means that if you haven’t started to use it, you better start doing it, as you’re not innovative and lagging behind. Is that true?</p> <p><strong>I think that’s too optimistic; we’re not there yet.</strong> Still, I believe that’s one of the emerging concepts that can change how we build our software. From my observation, it’s boiling under the surface. One of the reasons <a href="/pl/leaving_event_store/">why I decided to go solo</a> was that I got workshops and consulting requests, even though I didn’t advertise them. Of course, I’m part of the bubble, but trying to look realistically at it.</p> <p><strong>Event Sourcing passed the hype cycle.</strong> <a href="/pl/event_streaming_is_not_event_sourcing">Some people failed to use it and were loud about it</a>. Some were quieter about their failures because they learned from them and recovered. For many years, we had a shortage of practical resources about Event Sourcing; that’s one of the reasons why I started to build my <a href="https://github.com/oskardudycz/">samples</a> and <a href="/pl/introduction_to_event_sourcing/">self-paced kits</a> and write on <a href="/pl/category/#eventsourcing">this blog</a>. Right now, tooling matured, and we have already established patterns. Wild-wild west times are over. In that regard, InfoQ is right.</p> <p><strong>Half a year ago, <a href="/pl/share_your_story_on_event_sourcing_live/">I invited</a> the community to share their journey at the Event Sourcing Live conference.</strong> I wrote:</p> <blockquote> <p>We want to prove that Event Sourcing is a highly practical pattern and show its real-world usage during the Event Sourcing Live Conference. We want to learn about both big successes adopting it and horror stories. Your stories.</p> </blockquote> <p>And people accepted the challenge!</p> <p>Event Sourcing Live is the only conference focused on the Event Sourcing pattern and a spin-off of <a href="https://2023.dddeurope.com/">Domain Driven Design Europe</a>. We had the third edition of it. It was an honour for me to be invited by <a href="https://verraes.net/">Mathias Verraes</a> to be this year’s line-up curator.</p> <p><strong>The goal was to show that Event Sourcing is practical and pragmatic.</strong> We wanted to make it more hands-on, giving the space to show the tech stack. We encouraged speakers that they could (and should!) go down the rabbit hole. We also assumed that the audience should be familiar with the basics, so speakers don’t have to repeat the introduction stuff, like what’s an event, what’s projection and how to build a state from events. See:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/vTTCQ3-ZK1c?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>So if you were there and the introduction was missing, blame me, not the speakers.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4f09fd06ce8cb07dc694225263d7d4f4/4373c/2023-06-15-01.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAUCBP/EABQBAQAAAAAAAAAAAAAAAAAAAAL/2gAMAwEAAhADEAAAAeetK0zJAX//xAAaEAEBAAIDAAAAAAAAAAAAAAACAxESAQQz/9oACAEBAAEFAunE0GhJr6SKI2pl55X/xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAwEBPwGH/8QAFhEAAwAAAAAAAAAAAAAAAAAAARAR/9oACAECAQE/AYV//8QAHxAAAgECBwAAAAAAAAAAAAAAAAECAxESISIyQVFh/9oACAEBAAY/AnKXY8MUshmmo0b3bn0Z/8QAGBABAQEBAQAAAAAAAAAAAAAAAREAITH/2gAIAQEAAT8hGFZEuh+g8NRX26OPvhh1CDw9wpLd/9oADAMBAAIAAwAAABBn3//EABYRAQEBAAAAAAAAAAAAAAAAAAEAMf/aAAgBAwEBPxBS7O3/xAAXEQEBAQEAAAAAAAAAAAAAAAABEQAx/9oACAECAQE/EJHLgZv/xAAbEAEAAgIDAAAAAAAAAAAAAAABABEhQTFRYf/aAAgBAQABPxBYLQwDid+GZOHcBd9lxcvJqEXuU6CBAoFvlzkIMPmp/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4f09fd06ce8cb07dc694225263d7d4f4/c60e9/2023-06-15-01.jpg" srcset="/static/4f09fd06ce8cb07dc694225263d7d4f4/37402/2023-06-15-01.jpg 200w, /static/4f09fd06ce8cb07dc694225263d7d4f4/4cda9/2023-06-15-01.jpg 400w, /static/4f09fd06ce8cb07dc694225263d7d4f4/c60e9/2023-06-15-01.jpg 800w, /static/4f09fd06ce8cb07dc694225263d7d4f4/6c738/2023-06-15-01.jpg 1200w, /static/4f09fd06ce8cb07dc694225263d7d4f4/4373c/2023-06-15-01.jpg 1478w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>We started with a <a href="https://2023.dddeurope.com/program/kiss/">talk by Yves Reynhout</a> showing that Event Sourcing can be simple if we keep it like that.</strong> I’m happy that Yves came with this talk, and we intentionally put it as the first to make a clear stand. Event Sourcing is not complex, it’s different, and if we try to keep it simple, our journey will be much smoother. Yves managed to go through the most common (mis)conceptions and explain them, busting the most common myths.</p> <p>Still, we didn’t want just to show the easy part. We also wanted to show the challenges, including technical ones!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/60945c557e795c3cefc08f05c00b9871/4373c/2023-06-15-02.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAQBAgMF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAP/2gAMAwEAAhADEAAAAecxhepMgm//xAAaEAEBAAIDAAAAAAAAAAAAAAABAgADERIz/9oACAEBAAEFAtMDPXN3qDJTWUPP/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQMBAT8Bp//EABkRAAIDAQAAAAAAAAAAAAAAAAABAhITQf/aAAgBAgEBPwHOS4VZ/8QAGRAAAgMBAAAAAAAAAAAAAAAAABEBAiEQ/9oACAEBAAY/AnyzMtJko0//xAAYEAEBAQEBAAAAAAAAAAAAAAABEQBRQf/aAAgBAQABPyGpGY8yei6OMarmjS3NtC7/2gAMAwEAAgADAAAAEDwv/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAER/9oACAEDAQE/EJDX/8QAFxEAAwEAAAAAAAAAAAAAAAAAABEhAf/aAAgBAgEBPxDYKYiI/8QAGxABAAMBAQEBAAAAAAAAAAAAAQARITFhcaH/2gAIAQEAAT8QBIVWDwBN5Et6Mr5+QYfcMSKrjNDbWvlzFABYVgUT/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/60945c557e795c3cefc08f05c00b9871/c60e9/2023-06-15-02.jpg" srcset="/static/60945c557e795c3cefc08f05c00b9871/37402/2023-06-15-02.jpg 200w, /static/60945c557e795c3cefc08f05c00b9871/4cda9/2023-06-15-02.jpg 400w, /static/60945c557e795c3cefc08f05c00b9871/c60e9/2023-06-15-02.jpg 800w, /static/60945c557e795c3cefc08f05c00b9871/6c738/2023-06-15-02.jpg 1200w, /static/60945c557e795c3cefc08f05c00b9871/4373c/2023-06-15-02.jpg 1478w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Event Sourcing and serverless are an excellent match. Still, most popular event stores are built in the traditional way, which doesn’t allow an easy serverless model. Some people are trying to build event stores on DynamoDB and CosmosDB, but it’s tricky, and those implementations are still so early in the journey that I couldn’t recommend any of those tooling besides <a href="https://github.com/jet/equinox/">Equinox</a>.</p> <p><strong>That’s why I was thrilled that <a href="https://2023.dddeurope.com/program/event-sourcing-in-a-serverless-world/">Alexey Zimarev came up with the idea of talking about that</a>.</strong> He managed to step by step with the considerations on running and operating serverless solutions with existing tooling. He also showed potential solutions to make that scalable on the example of <a href="https://eventuous.dev/docs/connector/">Eventuous connectors</a>.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d74cd9ab67999ad06e4830147bdcbd7a/4373c/2023-06-15-03.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAUCBP/EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAHkuwtrOBf/xAAbEAACAwADAAAAAAAAAAAAAAACAwABEQQSMv/aAAgBAQABBQLhqBkERGN9gHWtZpVe/wD/xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAwEBPwGH/8QAFhEBAQEAAAAAAAAAAAAAAAAAABEB/9oACAECAQE/AZqP/8QAGxABAAICAwAAAAAAAAAAAAAAAQAhAgMQIjH/2gAIAQEABj8CXM8nUDis0lbEls//xAAbEAACAgMBAAAAAAAAAAAAAAAAAREhMUFxUf/aAAgBAQABPyG8joTxxEZ5Y9t1pF6i36P5kH//2gAMAwEAAgADAAAAEJcP/8QAFhEBAQEAAAAAAAAAAAAAAAAAAAEh/9oACAEDAQE/EC7X/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARUf/aAAgBAgEBPxDlCDL/xAAbEAEBAQEAAwEAAAAAAAAAAAABEQAhQVFxgf/aAAgBAQABPxCxBRCg/dElngMU7lBG3k0BtdTj9ypukGrft0AD4Gb/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/d74cd9ab67999ad06e4830147bdcbd7a/c60e9/2023-06-15-03.jpg" srcset="/static/d74cd9ab67999ad06e4830147bdcbd7a/37402/2023-06-15-03.jpg 200w, /static/d74cd9ab67999ad06e4830147bdcbd7a/4cda9/2023-06-15-03.jpg 400w, /static/d74cd9ab67999ad06e4830147bdcbd7a/c60e9/2023-06-15-03.jpg 800w, /static/d74cd9ab67999ad06e4830147bdcbd7a/6c738/2023-06-15-03.jpg 1200w, /static/d74cd9ab67999ad06e4830147bdcbd7a/4373c/2023-06-15-03.jpg 1478w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I promised you patterns. And with the definition of a pattern, <a href="https://2023.dddeurope.com/speakers/james-geall/">James Geall started his talk</a>. It was a wise move, as the process manager is one of the most misunderstood topics (see also more in <a href="/pl/saga_process_manager_distributed_transactions/">Saga and Process Manager - distributed processes in practice</a>). He neatly explained the importance of explicit business process modelling and how to manage workflows without hair loss. James also explained how to compose that with business logic and where to draw a line of responsibility. He also made us mind-boggled with (un)intentional mixture of colours of the event modelling sticky notes.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/989ece33effae4f4869c0cf38ce616d4/4373c/2023-06-15-04.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAgX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgP/2gAMAwEAAhADEAAAAeXfI3U0JJf/xAAZEAACAwEAAAAAAAAAAAAAAAABAgADEhH/2gAIAQEAAQUCoXszG6CteYug7aLf/8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8Bqv/EABgRAAMBAQAAAAAAAAAAAAAAAAACExFR/9oACAECAQE/AZthNuH/xAAaEAEBAAIDAAAAAAAAAAAAAAABAAIhEBIy/9oACAEBAAY/AnjSlrJIey3tv//EABwQAQACAQUAAAAAAAAAAAAAAAEAERAxQVFhcf/aAAgBAQABPyEnYxNbwGILuuI0wDUd40NHU//aAAwDAQACAAMAAAAQDA//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAwEBPxCEP//EABcRAAMBAAAAAAAAAAAAAAAAAAABESH/2gAIAQIBAT8QWhKlNH//xAAaEAEBAQEAAwAAAAAAAAAAAAABEQAhMUFx/9oACAEBAAE/EH68kdJwDCAiWIe9QF8qDOxiq59YNjVa7O7/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/989ece33effae4f4869c0cf38ce616d4/c60e9/2023-06-15-04.jpg" srcset="/static/989ece33effae4f4869c0cf38ce616d4/37402/2023-06-15-04.jpg 200w, /static/989ece33effae4f4869c0cf38ce616d4/4cda9/2023-06-15-04.jpg 400w, /static/989ece33effae4f4869c0cf38ce616d4/c60e9/2023-06-15-04.jpg 800w, /static/989ece33effae4f4869c0cf38ce616d4/6c738/2023-06-15-04.jpg 1200w, /static/989ece33effae4f4869c0cf38ce616d4/4373c/2023-06-15-04.jpg 1478w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>The next talk took us forward with those ideas. <a href="https://2023.dddeurope.com/program/aggregates-composition-a-new-view-on-aggregates/">Jérémie Chassaing showed us</a> how to compose business logic and process managers using <a href="https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider">the Decider pattern</a>.</strong> A lot of code, 100% hands-on mode and real examples. I can say that I’m under the huge influence of the Decider pattern (see, e.g. in <a href="/pl/how_to_effectively_compose_your_business_logic/">How to effectively compose your business logic</a>). If you haven’t checked it yet, <a href="https://github.com/thinkbeforecoding/dddeu-2023-deciders">try it on your own</a>.</p> <p><strong><a href="/pl/projections_and_read_models_in_event_driven_architecture/">I wrote that I had to choose the killer feature of Event Sourcing, I’d select projections.</a>.</strong> They’re great not only because they allow building customised read models from stored events. They allow decreasing in the cognitive load of the development process. We can break it down into two phases: capturing business facts and interpreting them.</p> <p><strong><a href="https://2023.dddeurope.com/program/zero-downtime-projections-replay/">Robert Baelde</a> and <a href="https://2023.dddeurope.com/program/projections-for-gamification-in-a-social-app/">Anton Stöckl</a> showed us how to deal with projections.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/331479b5889df412382719c5456ac76a/c60e9/2023-06-15-anton.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIEA//EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAFKUyKoQT//xAAaEAACAwEBAAAAAAAAAAAAAAABAgADIhIh/9oACAEBAAEFAkg8mY+S1pCVP0n/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPwGH/8QAHRAAAgIBBQAAAAAAAAAAAAAAAAECEQMQITFRYf/aAAgBAQAGPwJDu9+jiZjj4N1p/8QAGxAAAwEBAAMAAAAAAAAAAAAAAREhAEExofD/2gAIAQEAAT8hcAFCJjBIQB4b6AaE4jyjoVJ1awve/9oADAMBAAIAAwAAABAAP//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAEDAQE/EKr/xAAXEQADAQAAAAAAAAAAAAAAAAAAASER/9oACAECAQE/EFhwp//EAB0QAQACAgIDAAAAAAAAAAAAAAEAESExQVFhgfD/2gAIAQEAAT8Qa1VF9niEOCDZpmXN9dw2BTPclscGUpaxBjdKaLS6HL7n/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/331479b5889df412382719c5456ac76a/c60e9/2023-06-15-anton.jpg" srcset="/static/331479b5889df412382719c5456ac76a/37402/2023-06-15-anton.jpg 200w, /static/331479b5889df412382719c5456ac76a/4cda9/2023-06-15-anton.jpg 400w, /static/331479b5889df412382719c5456ac76a/c60e9/2023-06-15-anton.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Anton showed his journey and how to keep projection handling simple when you don’t need to reach the ultimate scale. All of that was backed by his personal experience working on the internal gamification solution.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b87738a84a2a84c62fe82d86f7b4e6fb/4373c/2023-06-15-05.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMEAf/EABUBAQEAAAAAAAAAAAAAAAAAAAED/9oADAMBAAIQAxAAAAGOmRlDBJN//8QAGRABAQEAAwAAAAAAAAAAAAAAAQISAxEi/9oACAEBAAEFAuOPEnRe2iEJ2Vet/wD/xAAVEQEBAAAAAAAAAAAAAAAAAAAQMf/aAAgBAwEBPwGn/8QAGBEAAwEBAAAAAAAAAAAAAAAAAAESAiH/2gAIAQIBAT8BjS6Sz//EABoQAQEAAgMAAAAAAAAAAAAAAAEAAhEQITL/2gAIAQEABj8COG6zb2s6dX//xAAcEAEBAQABBQAAAAAAAAAAAAABEQAhMUFRYaH/2gAIAQEAAT8hJ4DRH3nKX5gkZpNB3POssy9N/9oADAMBAAIAAwAAABBs/wD/xAAXEQADAQAAAAAAAAAAAAAAAAAAAREh/9oACAEDAQE/EHMUTmH/xAAWEQEBAQAAAAAAAAAAAAAAAAARACH/2gAIAQIBAT8QJhk0v//EABsQAQADAQADAAAAAAAAAAAAAAEAESExUYHw/9oACAEBAAE/EGRBr3A3iuzzcTmA1jGWQwHI27DqGJZrYWOfFT//2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b87738a84a2a84c62fe82d86f7b4e6fb/c60e9/2023-06-15-05.jpg" srcset="/static/b87738a84a2a84c62fe82d86f7b4e6fb/37402/2023-06-15-05.jpg 200w, /static/b87738a84a2a84c62fe82d86f7b4e6fb/4cda9/2023-06-15-05.jpg 400w, /static/b87738a84a2a84c62fe82d86f7b4e6fb/c60e9/2023-06-15-05.jpg 800w, /static/b87738a84a2a84c62fe82d86f7b4e6fb/6c738/2023-06-15-05.jpg 1200w, /static/b87738a84a2a84c62fe82d86f7b4e6fb/4373c/2023-06-15-05.jpg 1478w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Robert explained the second-day issue: rebuilding our projections without system interruption. That’s a complex part; we still don’t have that out of the box in Marten, but it’s one of our most important goals. That’s also why I’m happy that Robert shared his heuristics and explained the hard parts with potential solutions. I was especially intrigued by the idea of the snapshots representing a partial phase of projection to speed up the rebuild. That’s tricky, as it requires betting on when our interpretation can be stable enough, so we don’t have to rebuild it but can increase performance a lot.</p> <p><strong>One of the things that are too often missed while doing event-driven design is data governance practices.</strong> We’re taking things too lightweight. In some environments, it may work well, but for the bigger enterprises, that’s too optimistic if we want to use event-driven tools as a communication backbone.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b09784f1f2a6fb1ef903106cdd106fe9/4373c/2023-06-15-06.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAEEAwX/xAAWAQEBAQAAAAAAAAAAAAAAAAADAQL/2gAMAwEAAhADEAAAAeRbgnlAgt//xAAaEAACAwEBAAAAAAAAAAAAAAAAAgEDERIh/9oACAEBAAEFAqY2eTCtcMYdvf/EABURAQEAAAAAAAAAAAAAAAAAABAR/9oACAEDAQE/Aaf/xAAZEQADAAMAAAAAAAAAAAAAAAAAAQIREiH/2gAIAQIBAT8BmHjhsz//xAAaEAACAgMAAAAAAAAAAAAAAAAAARARMTJR/9oACAEBAAY/AsTabN2cP//EABoQAQEBAAMBAAAAAAAAAAAAAAERACExUdH/2gAIAQEAAT8hN7WgODNe4nO7yH1zolg3/9oADAMBAAIAAwAAABCk7//EABYRAAMAAAAAAAAAAAAAAAAAAAEQEf/aAAgBAwEBPxCAv//EABcRAAMBAAAAAAAAAAAAAAAAAAABEVH/2gAIAQIBAT8QZhKLA//EABsQAQADAQADAAAAAAAAAAAAAAEAESFBYYHB/9oACAEBAAE/EBVqmUXssKDDkBWq9S8zoZ3E+ykCAvVCIVvmf//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b09784f1f2a6fb1ef903106cdd106fe9/c60e9/2023-06-15-06.jpg" srcset="/static/b09784f1f2a6fb1ef903106cdd106fe9/37402/2023-06-15-06.jpg 200w, /static/b09784f1f2a6fb1ef903106cdd106fe9/4cda9/2023-06-15-06.jpg 400w, /static/b09784f1f2a6fb1ef903106cdd106fe9/c60e9/2023-06-15-06.jpg 800w, /static/b09784f1f2a6fb1ef903106cdd106fe9/6c738/2023-06-15-06.jpg 1200w, /static/b09784f1f2a6fb1ef903106cdd106fe9/4373c/2023-06-15-06.jpg 1478w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://2023.dddeurope.com/program/event-driven-architecture-and-governance-in-action/">Wim Debreuck explained</a> why and how we can and should think more responsibly about defining our event model.</strong> He showed how to put our events in a broader context. He used Kafka as an example. Event Streaming is not the same as Event Sourcing, but both come from event-driven tooling, and many patterns are the same, and we can learn from each other. It’s essential to think about what should happen with the events we store, as it’s just the beginning of the journey.</p> <p><strong>To close with the personal experience, we also had talks from Anita Kvamme and Łukasz Reszke. They shared insights and lessons learned from their real projects.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f5139c0ebc8bff4c2bb8d4d973632fde/c60e9/2023-06-15-lukasz.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAQDBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAeBZJsTg/8QAGRAAAwEBAQAAAAAAAAAAAAAAAAECAwQQ/9oACAEBAAEFAjoznPxGlO2f/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGBAAAgMAAAAAAAAAAAAAAAAAARAAERL/2gAIAQEABj8Cgy7K/8QAGhAAAQUBAAAAAAAAAAAAAAAAAQAQESExYf/aAAgBAQABPyEWUck1t4qQzxv/2gAMAwEAAgADAAAAEGTP/8QAFhEBAQEAAAAAAAAAAAAAAAAAARAR/9oACAEDAQE/EBMZ/8QAFxEBAAMAAAAAAAAAAAAAAAAAARARIf/aAAgBAgEBPxCl2P/EABsQAQEAAgMBAAAAAAAAAAAAABEBABAhMVHB/9oACAEBAAE/EICI0bkqpfFOvujysXxw1BDga//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/f5139c0ebc8bff4c2bb8d4d973632fde/c60e9/2023-06-15-lukasz.jpg" srcset="/static/f5139c0ebc8bff4c2bb8d4d973632fde/37402/2023-06-15-lukasz.jpg 200w, /static/f5139c0ebc8bff4c2bb8d4d973632fde/4cda9/2023-06-15-lukasz.jpg 400w, /static/f5139c0ebc8bff4c2bb8d4d973632fde/c60e9/2023-06-15-lukasz.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="https://2023.dddeurope.com/speakers/lukasz-reszke/">Łukasz had an intriguing journey</a> moving from the .NET community to Ruby and joining a company that’s not only responsible for delivering software for clients but also maintaining their own event store and being one of the Event Sourcing promoters in the DDD space.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3cd3ed0aa5b205caa139651f244055ee/4373c/2023-06-15-07.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 75%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAUBAgME/8QAFQEBAQAAAAAAAAAAAAAAAAAAAgD/2gAMAwEAAhADEAAAAVjHmslBiG//xAAZEAACAwEAAAAAAAAAAAAAAAABAgAREgP/2gAIAQEAAQUC4LcxHsEc6VdB20T/AP/EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAEDAQE/AVf/xAAYEQADAQEAAAAAAAAAAAAAAAAAARESAv/aAAgBAgEBPwHPUqMNn//EABkQAAIDAQAAAAAAAAAAAAAAAAABETEyAv/aAAgBAQAGPwKii4IXTRps0f/EABsQAQACAgMAAAAAAAAAAAAAAAEAESFBMVFx/9oACAEBAAE/Ic9bdMaEUwLDTGAFziESnNMF4Z7P/9oADAMBAAIAAwAAABDj7//EABcRAQADAAAAAAAAAAAAAAAAAAEQETH/2gAIAQMBAT8QKxh//8QAFxEBAAMAAAAAAAAAAAAAAAAAABExUf/aAAgBAgEBPxDYLEP/xAAcEAEBAQACAwEAAAAAAAAAAAABEQAxgSFBYXH/2gAIAQEAAT8QvfFCHi7g0dZnOakPbgcJRUazl611zK9HMSjwCz5+b//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/3cd3ed0aa5b205caa139651f244055ee/c60e9/2023-06-15-07.jpg" srcset="/static/3cd3ed0aa5b205caa139651f244055ee/37402/2023-06-15-07.jpg 200w, /static/3cd3ed0aa5b205caa139651f244055ee/4cda9/2023-06-15-07.jpg 400w, /static/3cd3ed0aa5b205caa139651f244055ee/c60e9/2023-06-15-07.jpg 800w, /static/3cd3ed0aa5b205caa139651f244055ee/6c738/2023-06-15-07.jpg 1200w, /static/3cd3ed0aa5b205caa139651f244055ee/4373c/2023-06-15-07.jpg 1478w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="https://2023.dddeurope.com/program/event-sourcing-in-action-insights-from-two-real-life-projects/">Anita showed</a> that whether something is good or bad practice depends on context. She talked a lot about the challenges of implementing an event store on top of CosmosDB and design tradeoffs they took. I liked the pragmatic way of showing the thought process and the existence of the grey area, e.g., splitting for <a href="/pl/events_should_be_as_small_as_possible/">private and public events</a> may also depend on the team structure. If we’re a single team, maybe we can take shortcuts. That may work if we’re making conscious, transparent decisions.</p> <p>Let’s not forget about the friendly folks from Event Store. <a href="https://2023.dddeurope.com/program/sponsored-talk-beyond-the-hype-an-interactive-exploration-of-event-sourcing-and-eventstoredb/">Yves Lorphelin and Alexey Zimarev did a discussion panel</a> during the main conference giving a chance to the community to ask questions and get answers. I enjoyed that primarily because it focused on the patterns and were not vendor specific.</p> <p><strong>I’d like to thank all the speakers for bearing with my lame MC jokes and all the community that was there showing the power of Event Sourcing!</strong></p> <p>All speakers did their best and delivered important content. If something was wrong, blame me, humble line-up curator.</p> <p>Being the curator and MC was a big thing but also stressful for me. <a href="/pl/agile_vs_introverts/">I’m an introvert</a> that somehow managed to keep the stress on a leash when giving a talk, but that was a new experience. I was really stressed. This is an interesting thing, as I knew that no one would be focused on what I was doing, as speakers and their talks were the most important. Still, I guess we humans are selfish, and our brain tries to focus on ourselves. Still, I tried to make the show as fluid as possible so both audience and speakers liked it.</p> <p><strong>The general feedback I got was positive.</strong> Of course, some people said it was too much tech stuff, but we intended to show that Event Sourcing is a living thing and people are doing real things. Some said they would like more content about specifics of event modelling, and I take that, although the content is also dependent on the submissions. I hope we’ll have more on that if we have the next edition.</p> <p>Still, I saw that people enjoyed the versatility, hands-on and pragmatic talks. I also liked them a lot. Especially since the content showed many faces of Event Sourcing, and most importantly, had this personal touch and was closed to the real projects, without esoteric considerations.</p> <p>When will the videos be available? I’m not sure, but I will update this article as soon as they are available. You can subscribe to <a href="https://www.architecture-weekly.com/">Architecture Weekly</a>, and I’ll definitely send an update there.</p> <p><strong>Let me finish this article with my favourite quote from this edition of Event Sourcing Live. Anita Kvamme said:</strong></p> <blockquote> <p>Event Sourcing is architecting for tomorrow’s questions</p> </blockquote> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[A few words on communication]]>https://event-driven.io/en/a_few_words_on_communication/https://event-driven.io/en/a_few_words_on_communication/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c7ccf546ef760d06d2c0e4fc629d0551/a331c/2023-06-11-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACZ0lEQVQoz2N4PvHL84lfPkz/86T305O+Ty8nfns39RdEEI6eTfz8duqvJ32fjlffWJ9xaF3mwfs9b15P/s7wfOKXV5O/z4hdF2tbkWBfmeZSt6ng8OvJP55N+AzVOeHzm8k/DlReineo8LZNdTVP0FXwTXOrvdf9muHjjL8LE7eZ6gRFhuQX5janp1WFOuTebHvxasr3ZxM/Q6x9N/1XW/xMD4/4wuyWzOTqyKD8IP/0TaUHGV5P+p7l2hQVnp8aU9FaN6W8pCPCtuhB17uXk79BND+d8OnL3P+zU9bFRBQWZ7fFhhaH++faGIcvTN7M8GrStxjrUlf7WD/nDB/HNEudcHUBz5Vpez7M+PN04qdnk748n/Tl+ZTP0ValDhaRlprh5qqhysJuuoIBl5sfMnQHz5dgtJNhc1DkdlET8pDndpHhcthUePjDTJDm51O+PJv0+c3UHzkuLSoSrlJMDhKM9vwM5hPDln+Z/Z9BjMkm1qgy2qCcn8FcktFemsVRXzDwSNWVd1N/PQWH2ZP+j19m/Z8euUaCwU6GxUma2VGK2X5X8akP0/8wiDBY20rHmolFCDJYqvJ4bso5er/nzaO+Dy9QY+tx34dLzfed5BJEGWyEGawi9UvfTvvBIM3sKMpoI85kK8JgPSVi1fe5/19M/Ppi0le0eH4x8evbqT/8NXIFGSxkWJzEGGwWJ29jkGJykGF2EmOwNRQOvtv96tXkb/AYhqOnEz69n/57b8lZaWYHSUYHGWZHUQYbHUE/kGYIkmC0XZm2++OMv0/7P6Fphni71XcOL4OpLIuTFJODNJOjOKMtAOHkW7QKumKUAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/c7ccf546ef760d06d2c0e4fc629d0551/a331c/2023-06-11-cover.png" srcset="/static/c7ccf546ef760d06d2c0e4fc629d0551/36ca5/2023-06-11-cover.png 200w, /static/c7ccf546ef760d06d2c0e4fc629d0551/a3397/2023-06-11-cover.png 400w, /static/c7ccf546ef760d06d2c0e4fc629d0551/a331c/2023-06-11-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I got a surprising question during my workshop this week:</strong></p> <blockquote> <p>It seems that communication with the business is critical to make Event Sourcing right. What would you recommend to learn how to talk with the business?</p> </blockquote> <p>And I have frozen for the moment. It sounded like a common question, but I had an issue responding promptly. A lot of thoughts started spinning around my head.</p> <p>For me, it was more of a question of how to communicate with others. There’s no significant difference between the business and developers. We’re all humans.</p> <p><strong>So my first answer, after the moment of silence, was: Empathy.</strong></p> <p>And I got a blank stare and quickly realised that wasn’t a great answer. Not because it’s incorrect. Empathy is the key here. But how to teach that to others or give actionable advice for others to work on that?</p> <p>Especially since we’re not taught about that during our studies, IT idols also rarely give a good example. We’re working in an industry where the running joke is: <em>I didn’t want to be a developer to talk to humans</em>.</p> <p>Still, much of our work is related to communication: meetings, Slack discussions, emails, etc. I already wrote a dedicated article with <a href="/en/fifteen_tips_on_how_to_run_meetings_effectively/">tips on running meetings effectively</a>. Even if we’re fully async, we <a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">write docs</a>, commit messages. All of that is communication. Even code is a form of discussion with our colleagues of future us.</p> <p><strong>It’s clear that even if we don’t want to talk to humans, we need to do it, which won’t change whether we like that.</strong></p> <p>OK, then, how to communicate better? Here are a few suggestions:</p> <h2 id="1-dont-use-jargon-or-acronyms-or-assume-someone-should-know-something" style="position:relative;"><a href="#1-dont-use-jargon-or-acronyms-or-assume-someone-should-know-something" aria-label="1 dont use jargon or acronyms or assume someone should know something permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>1. Don’t use jargon or acronyms or assume someone should know something.</h2> <p>Using terms like eventual consistency, scalability, and maintainability doesn’t say anything. 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. Then you may get annoyed, even if you’re the one that made a false assumption.</p> <h2 id="2-what-you-tell-is-not-what-they-hear" style="position:relative;"><a href="#2-what-you-tell-is-not-what-they-hear" aria-label="2 what you tell is not what they hear permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>2. What you tell is not what they hear.</h2> <p>Let’s assume that you have a brilliant thought. You want to express that but cannot choose the right words. Eventually, you manage to do that, but it sounds much different to you when you say it. And it sounds even different for other people. Each of these steps is a translation process. Even if you speak the same language, the understanding may differ. And that works both ways. The person trying to answer you will go through the same process. So again, don’t assume. Are you not sure? Ask, and double-check.</p> <h2 id="3-do-not-take-others-behaviour-personally" style="position:relative;"><a href="#3-do-not-take-others-behaviour-personally" aria-label="3 do not take others behaviour personally permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>3. Do not take others’ behaviour personally.</h2> <p>People do not mean bad for you; people just want good for themselves. Humans are rather lazy; we are looking for easy and straightforward solutions for ourselves. We’re rarely so important that others will go to great lengths to hurt us. One may harm us if we stand in the way of their goals. It’s usually just a business. We need to select the hills we’d like to die on. Usually, they’re not worth it.</p> <h3 id="4-be-critical-and-sceptical" style="position:relative;"><a href="#4-be-critical-and-sceptical" aria-label="4 be critical and sceptical permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>4. Be critical and sceptical.</h3> <p>Especially towards yourself and your ideas; First, listen, then talk. Too often, we already know what to tell without listening to someone. We just wait to tell our line. Will you lose much by listening to someone else’s ideas? You don’t need to agree with them, but they can give you an intriguing perspective.</p> <h3 id="5-be-curious-about-the-business-domain" style="position:relative;"><a href="#5-be-curious-about-the-business-domain" aria-label="5 be curious about the business domain permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>5. Be curious about the business domain</h3> <p>Be interested in others. Try to understand the domain you’re in. Don’t just solve coding sudoku. Talk with business, and accept <a href="/en/bring_me_problems_not_solutions/">that we should bring solutions, and they should bring problems</a>. Or best don’t make the distinctions and categorisations. The business/IT split is obsolete. Now IT is business.</p> <h3 id="6-be-assertive" style="position:relative;"><a href="#6-be-assertive" aria-label="6 be assertive permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>6. Be assertive</h3> <p>Assertive is not about <em>saying no</em>. It’s about clearly drawing boundaries and expressing them. Too often, I hear complaints that <em>the business won’t let me use X or do Y</em>. Most of that comes from a misunderstanding of why we’re here in the projects. We’re to solve business problems and deliver value using technology. If we are hired because of our tech skills, we need to make technical decisions. Yet, such decisions are not made in a vacuum. Time, budget and scope are also essential factors in making them. We cannot just make them based on purely technical assumptions. Still, we shouldn’t be making them and only take business assumptions. We should not be hiding from responsibility. We should have a trust agreement on the parts who are responsible for what and just make decisions in the areas that are purely in our area. Yes, that means that we can be made accountable for them. But that knowledge usually also increases the quality of making them.</p> <h3 id="7-its-ok-to-agree-to-disagree" style="position:relative;"><a href="#7-its-ok-to-agree-to-disagree" aria-label="7 its ok to agree to disagree permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>7. It’s OK to agree to disagree</h3> <p>We don’t need to have the same perspectives. Too often, I see people vigorously trying to persuade others to their point of view. Sometimes we can say that we respect other people’s opinions but <a href="https://www.youtube.com/watch?v=6ul-cZyuYq4">go our own way</a>. That’s fine as long as it’s not breaking’s other people work. Consensus is when we all win, and compromise is when we all lose the less. Agreeing to disagree is a compromise, and sometimes that’s good enough. We can always revisit our discussion later, wiser by the outcome of our decisions.</p> <p>Is it all that simple?</p> <p>It’s not.</p> <p>Is this a good starting point?</p> <p>I hope so.</p> <p><strong>The rest is up to you; a blog entry cannot teach you empathy.</strong></p> <p>Practice, practice, practice!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If you’re looking for a better starting point, check <a href="https://www.goodreads.com/book/show/4865.How_to_Win_Friends_and_Influence_People">Dale Carnegie - How to Win Friends &#x26; Influence People</a>. The title sounds cheesy, but it’s a great book with actionable advice. Also quick to read.</p> <p>A bit harder, but great reads are <a href="https://www.goodreads.com/book/show/714344.Becoming_a_Technical_Leader">Becoming Technical Leader</a> and <a href="https://www.goodreads.com/book/show/583766.An_Introduction_to_General_Systems_Thinking">An Introduction to General Systems Thinking</a> by Gerald M. Weinberg. The former will tell you how to advance reasonably in your career, the latter how to think critically, where to simplify and where to complicate your thinking.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[The Holy Grail syndrome]]>https://event-driven.io/en/holy_graal_syndrome/https://event-driven.io/en/holy_graal_syndrome/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3ec9feb6a907cbbf183677ad17e6cbd3/a331c/2023-06-01-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AAEFFgQUMAggPQghSA0sWxU8ZSRPgjxnqFuGxXKd2nal5Hyv64K39HWv8oW6+I/C/aTJ/dDp/8rl/8jl/wADEzEGHDoJIj0LKEgWN10fPWVSWG5SaplVg8RklNB5rOmHvft+ptmMseKj1f+z3v7B5v/P8//W9//W9/8ABx06Bxw/Bx0+DS1KHjlYICMrMzc1TWmXY5TWfq3ok7/5irr4coq2lbPYwev/xOv90Pb/zO//0/L/1fb/AAMUJAYZMQkhORsqPTUZKzAcGUZJVGOGw36n65zA/Lvb/7PQ9Ymbu77K5MHW7uD2/uf9/+/+//X///f//wA/S1E8TlNIWWpoZHQxGxJDMiFWQDSXmrzA0//H3f7a7f7s/P+rvt6FmcLu8fb////+/v////////////8Ap5qnp5yrsqi3c2VoGRwRPTYlYUgmdm922d3v6ff/8f3++v//1N7tvMvj/////v7/////////////////AJyeq6Gis6mtuV5gXBggGyYxMlljX7zM5sHb98zk/N/z/+34/t7n/Ovy/P////n+//n9//b7//T7//L4/wB4lLSBnbmLrMSSrsKKn7BLUViDkJ7FzdyswNGuw9W2y+Hr8Pnz+//x9/7u9P3o8P/k6v7U3fvP2/zQ2/wAepOXf5aRgpeVlKKfaXF0Ii8oQlBFs62vsbS+uLe8w7/IsrS4oLG8wM7n1N30ztn1w9T1v9L6wNb9wtT7AD5vPkZzP0J4NTdWPBcnFU5yTT5VO2uFb36khXifgIihhF52a0xtd4avvJ/H06XH3aTM8KTL96bM/KzO/AAdTRUyVBg3UxYZLAslUxpJdzk6YCs7VydNeURIeDxQgkhnjHVykYx6nZeHtLiSv9GRvOmTv/Sfxfeewvbxg5gAv7K7BAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/3ec9feb6a907cbbf183677ad17e6cbd3/a331c/2023-06-01-cover.png" srcset="/static/3ec9feb6a907cbbf183677ad17e6cbd3/36ca5/2023-06-01-cover.png 200w, /static/3ec9feb6a907cbbf183677ad17e6cbd3/a3397/2023-06-01-cover.png 400w, /static/3ec9feb6a907cbbf183677ad17e6cbd3/a331c/2023-06-01-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Our industry is harmed by the disease of <em>“There has to be something more!”</em> also called <em>“The Holy Grail syndrome”</em>.</strong></p> <p>Instead of trying to understand building blocks as they are, we’re trying to mix all of them, hoping that it will magically work and solve our problems.</p> <p><strong>When we see a simple definition, we can’t believe that it’s such simple, <em>“there has to be something more!”</em>.</strong></p> <p>And we try to find that even if it doesn’t exist.</p> <p>For instance, in the recent discussion I had about CQRS, a few people said: <em>“What’s the benefit of the CQRS if it’s just structural pattern?”</em> implying that there has to be more to it, and the typical complexity assigned to it is justified.</p> <p><strong><a href="/en/cqrs_facts_and_myths_explained/">Instead of thinking about why the pattern was designed like that and what it enables</a>; we already start thinking about the implementation details.</strong> So instead of noticing that allows evolving architecture, focus on business and reduce the risk of change, we immediately throw all the accidental complexity related to the tech stack.</p> <p>We should learn patterns, use cases as they are, and how to compose them to build bigger structures. Most patterns are simple in a nutshell; what makes them challenging is their interactions with others.</p> <p><strong>If we don’t understand how to use them but constantly mix them, we’ll end up with a massive hangover.</strong></p> <p><strong>I noticed that we too often start with complex solutions and then try to simplify them.</strong> We should do it the other way round. Start simple and add complexity where needed. Such a way is more manageable, but also…</p> <p>…less sexy.</p> <p>Why does it have to be sexy?</p> <p>In our industry, most people don’t have contact with end users. Moreover, most don’t even have contact with domain experts and can’t make product decisions. As they don’t see the business outcome and can not impact the product design, they focus on what they can feel responsible for. And usually, that’s code. Then they’re toying with the tech stack to feel that they can make impact.</p> <p>Using a boring stack provides the best business results, but you must see them to enjoy them. If people can’t, then they search for other enjoyments. Plus, we are spoiled by not being accountable for what we do and that <a href="https://www.youtube.com/watch?v=2dKZ-dWaCiU">odd craftsmanship, clean coders tech bros mambo jumbo</a>.</p> <p>Of course,</p> <p><strong>I talked longer on that in <a href="/en/the_magic_is_that_there_is_no_magic/">The magic is that there is no magic. Or how to understand design patterns.</a> but I’d like to repeat that: start simple, grow big.</strong></p> <p>Be frank with yourself. Try to avoid complexity where it’s not because you’ll end up with an over-complicated solution, at least if you don’t want to resemble a <a href="https://www.youtube.com/watch?v=3XPnIUtcANg">knight, riding an imaginary horse, making sounds with coconuts</a>.</p> <p><strong><a href="https://hachyderm.io/@nick_tune/110470101137078440">Don’t be a Coconut Architect!</a></strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Of course, we should provide simple solutions, but not simplistic ones. How to make the right decisions? By proper risk management, fast feedback loop and continuous reevaluation of our assumptions. Read more in:</p> <ul> <li><a href="/en/the_risk_of_ignoring_risks/">The risk of ignoring risks</a></li> <li><a href="/en/why_are_we_afraid_of_our_decisions/">Why are we afraid of our decisions?</a></li> <li><a href="/en/chesterton_fence_and_software_architecture">What do the British writer and his fence have to do with Software Architecture?</a></li> </ul> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Anti-patterns in event modelling - I'll just add one more field]]>https://event-driven.io/en/i_will_just_add_one_more_field/https://event-driven.io/en/i_will_just_add_one_more_field/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0b8fa1798ac1285b698c40a03b6cf6db/a331c/2023-05-28-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AGWJLmKHLWOHLGuOM2iLMmKFK2CDL2aJM2SJLnOWP4SgVnKSQmSHMmGHMV2ELliALFB7Kkl1JkRuITxlHgBggihjhitniS1ukTdzlTxukjRqkDJrkTODpGN8l2SEl4hcdUlbhChchCxcgytagStTfSpMeCZNdCRBaiEAWoIlXYMcXYYhXYEZY4snX4wtX40sZJEwPXc/GVM2Fk0sJlYnU4InVoIoUoAmSngkPmcgSnMjUnslUXgkAE59JUqag0uyvWa7qVaUV1uMJF+QLl6OJzJ3MxliNBZWKideI16LKlyHKVSCJk59JkhxI0p0I0d1IUl0IQBOgDI1kq1GnMA+jJtBektkkTOApGGXuXw4fjsZYzQTWChCc0hsiUpSfCdTfSNNdiFQeClEch5FbiJBaiAAXIE2UncqYnkzmLB8us+kytm94+jkxNHHTY5gD14tFVEmLFU6OU0/PFo+jqqKcpFWZIlBe5xeU3gsSnIjAFmBNWyHV3SQZJW4m7rFv2x1cbzHw+Hw7XuOdypeN2WXgHWVjEVYVU1cV5Wqmdfl02R6WlVpTlV5NUtyIgBWei9mg01ObzNbdEdogGdtgHe0x7Omz6iLnINxjXZRc2Q+UkI0RD6xx72Hqnzh7N6ps7IlRxdOcyhLcSIAUXUlT3ElSWwqNWIYfqt61/f3rsmxvcqzxdvXY4B3JzEuMDk2Q1RPc4yCd55yYoRSla1+k61xUHw3U385AEVqIUhoKk1kLWqLUXadZsfVxLTJsoKgckFYQjRHQCw+MCg7JiQ2I2J6YFF5QDBZHj9iIWJ/QUdvOFh+RQBFaCI/ZiFRbSqTlmpsh1trg1tBbCU9ax8+ZCM7XSY6ZSc5Zik1ZCFQeTpOdDZRazkyQiZAYSuPqIV9lW8vjgLTmGo1kAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/0b8fa1798ac1285b698c40a03b6cf6db/a331c/2023-05-28-cover.png" srcset="/static/0b8fa1798ac1285b698c40a03b6cf6db/36ca5/2023-05-28-cover.png 200w, /static/0b8fa1798ac1285b698c40a03b6cf6db/a3397/2023-05-28-cover.png 400w, /static/0b8fa1798ac1285b698c40a03b6cf6db/a331c/2023-05-28-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Programming origins are in mathematics. Scientists like <a href="https://en.wikipedia.org/wiki/Von_Neumann_architecture">John Von Neumann</a> and <a href="https://en.wikipedia.org/wiki/Turing_machine">Alan Turing</a> built the foundations for today’s computers.</p> <p>That may be a reason why we tend to believe that 1 plus 1 will always equal 2.</p> <p><strong>This fact is visible in our data modelling; we try to put as much data as possible. Just in case we need it.</strong> There may be a valid reason for that. I’m also to blame, as I often repeat that <a href="/en/never_lose_data_with_event_sourcing/">storage is cheap, but the information is priceless</a>. Still, the more is not always the merrier.</p> <p>Let’s look at the following events:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CustomerCreated</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CustomerId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Email <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentLogged</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> CustomerId<span class="token punctuation">,</span> <span class="token class-name">Contact</span> Contact<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Description<span class="token punctuation">,</span> <span class="token class-name">Guid</span> LoggedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> LoggedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentResolved</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">ResolutionType</span> Resolution<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ResolvedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ResolvedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ResolutionAcknowledgedByCustomer</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> AcknowledgedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> AcknowledgedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentClosed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClosedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ClosedAt <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>They’re part of the IT Helpdesk domain I described in <a href="/en/projections_in_marten_explained/">Event-driven projections in Marten explained</a> article.</p> <p>Now, we’d like to build a read model that contains the summary of the incidents broken down by status:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerIncidentsSummary</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Pending <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Resolved <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Acknowledged <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Closed <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We could build such projection by applying events in the following way:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerIncidentsSummaryProjection</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentLogged</span> logged<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> current<span class="token punctuation">.</span>Pending<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentResolved</span> resolved<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> current<span class="token punctuation">.</span>Pending<span class="token operator">--</span><span class="token punctuation">;</span> current<span class="token punctuation">.</span>Resolved<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ResolutionAcknowledgedByCustomer</span> acknowledged<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> current<span class="token punctuation">.</span>Resolved<span class="token operator">--</span><span class="token punctuation">;</span> current<span class="token punctuation">.</span>Acknowledged<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentClosed</span> closed<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> current<span class="token punctuation">.</span>Acknowledged<span class="token operator">--</span><span class="token punctuation">;</span> current<span class="token punctuation">.</span>Closed<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Based on the events, we’re progressing from one state to another and increasing or decreasing the number of incidents in the specific state.</p> <p><strong>It seems fine, but we must correlate the events with the read model id. As it’s the customer summary, we could use the customer id as the key.</strong> Yet, the careful reader will notice that we have customer id only in the <em>IncidentLogged</em> event. Why? Because we assume that the customer id won’t change during the incident’s lifetime. Repeating this information would be redundant.</p> <p>Still, that makes our projections processing harder, as we cannot easily correlate data.</p> <p><strong>The first idea might be: <em>“Easy peasy. Let’s add the customer id to all the events!”</em></strong></p> <p>That may sound tempting, as it’s just one more property.</p> <p><strong>Plus, we’ll have more data on the events, which means we’ll also have more information.</strong> That sounds like an obvious move, then? Nope.</p> <p>We need to remember that one of the most powerful features of events is that they capture precise business context. This is powerful, as we can understand the workflow by looking at them. They’re a form of documentation and a bridge between us and business people.</p> <p>If we add customer id to, e.g. <em>ResolutionAcknowledgedByCustomer</em> event</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ResolutionAcknowledgedByCustomer</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> CustomerId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> AcknowledgedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> AcknowledgedAt <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then what would that mean? If we’re the person that added the customer id to the event, we may remember the tradeoff. But if we’re the new person in the project, a colleague from the other team or future ourselves, we may wonder why this customer id is there.</p> <p>We may need to double-check if this redundancy was a tradeoff, or the incident may be shared between customers, or we can transfer the incident and resolve it on the other customer.</p> <p>You may say that I’m exaggerating, but usually, after the first redundant property comes another one and another one. Each illegal dump starts with the first person throwing junk in the open space.</p> <p>Then cognitive load skyrockets.</p> <p><strong>If we add too much information to our events, they will lose precision.</strong> It may look counterintuitive that by adding more information, we’re losing it, as it’s getting harder and harder to interpret the meaning. So 1 plus 1 may equal 0.</p> <p>Of course, a tradeoff is a tradeoff. We’re trading complexity for <a href="/en/the_risk_of_ignoring_risks/">the risk</a>. That’s all fine if we’re keeping it on a leash. How is the spaghetti code made? Day by day.</p> <p>Read more about it in <a href="/en/events_should_be_as_small_as_possible/">Events should be as small as possible, right?</a>.</p> <p><strong>So what other alternatives do we have?</strong></p> <p><strong>One of the options is to find the customer id for the specific incident and use it to correlate data.</strong> Yet, how to do it? I explained in <a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">How to create projections of events for nested object structures?</a> that it’s more challenging than it seems.</p> <p><strong>If we had an Incident read model, we could do the lookup or join if we keep the data in a relational database.</strong> It could be a solution and a tradeoff, as then we’d couple the read models together. That would make <a href="https://event-driven.io/pl/projections_and_read_models_in_event_driven_architecture/#projections-rebuild">projections rebuild</a> much harder. We’d always need to rebuild those projections and create a <em>rebuild train</em> to rebuild them in a specific order. That’s, of course, a potential trap that we’ll fall into later.</p> <p><strong>Alternative is to load additional data from other events.</strong> If we assume that <em>IncidentLogged</em> always comes as the first one, we could query it based on the Incident stream id. In Marten we could use a custom grouper feature:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerIncidentsSummaryProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">MultiStreamProjection<span class="token punctuation">&lt;</span>CustomerIncidentsSummary<span class="token punctuation">,</span> Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">CustomerIncidentsSummaryProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CustomerCreated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">CustomGrouping</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CustomerIncidentsSummaryGrouper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerIncidentsSummaryGrouper</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IAggregateGrouper<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Type<span class="token punctuation">[</span><span class="token punctuation">]</span></span> eventTypes <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">IncidentResolved</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">ResolutionAcknowledgedByCustomer</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">IncidentClosed</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Group</span><span class="token punctuation">(</span><span class="token class-name">IQuerySession</span> session<span class="token punctuation">,</span> <span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>IEvent<span class="token punctuation">></span></span> events<span class="token punctuation">,</span> <span class="token class-name">ITenantSliceGroup<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span> grouping<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> filteredEvents <span class="token operator">=</span> events <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>ev <span class="token operator">=></span> eventTypes<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>ev<span class="token punctuation">.</span>EventType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>filteredEvents<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> incidentIds <span class="token operator">=</span> filteredEvents<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>StreamId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">QueryRawEventDataOnly</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>e <span class="token operator">=></span> incidentIds<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>IncidentId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>x <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> x<span class="token punctuation">.</span>IncidentId<span class="token punctuation">,</span> x<span class="token punctuation">.</span>CustomerId <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> <span class="token keyword">group</span> <span class="token keyword">in</span> result<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>g <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> g<span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> Events <span class="token operator">=</span> filteredEvents<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>ev <span class="token operator">=></span> ev<span class="token punctuation">.</span>StreamId <span class="token operator">==</span> g<span class="token punctuation">.</span>IncidentId<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> grouping<span class="token punctuation">.</span><span class="token function">AddEvents</span><span class="token punctuation">(</span><span class="token keyword">group</span><span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> <span class="token keyword">group</span><span class="token punctuation">.</span>Events<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Yet, it’s undeniable that this code is already complicated, and we’re also getting an N+1 problem, as each time we get one of the defined events, we need to do an additional query. Of course, we could use some cache, batch processing etc. But that already sounds hairy.</p> <p>What else could we do?</p> <p><strong>We could use event metadata.</strong> Each event is built from the payload and additional information. That additional information is built from</p> <ul> <li>common properties like stream id, event type name, position in the stream, timestamp, etc.</li> <li>user-defined properties.</li> </ul> <p>Most event stores provide the possibility to provide custom event metadata (see docs for <a href="https://martendb.io/events/metadata.html">Marten</a>, <a href="https://developers.eventstore.com/server/v22.10/streams.html#event-metadata">EventStoreDB</a>, <a href="https://docs.axoniq.io/reference-guide/axon-framework/messaging-concepts/anatomy-message">Axon</a>). We can put some common properties that are not part of the business process but help us make technical processing easier.</p> <p>Of course, we need to be careful not to use it too much to cheat and make it our open dump for everything. <strong>Read more in <a href="/en/projections_and_event_metadata/">Using event metadata in event-driven projections</a>.</strong></p> <p><strong>Still, providing a customer id as metadata sounds like a decent move for our case.</strong> That looks like something that could potentially be a reusable concept, also for diagnostics and tracing. We could use it to correlate our event with the read model.</p> <p>We need to remember that metadata is usually kept as a separate field in event stores. Getting data from it require deserialisation which may have an impact on performance.</p> <p><strong>So we should also not put too much into metadata just in case.</strong></p> <p>Any other alternatives? Sure!</p> <p><strong>We can accumulate incident information in the read model.</strong> It may also be closer to real life. Our assumption that we always have a forward transition is quite naive. We may soon realise that we need to expand our solution. For instance like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IncidentInfo</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">int</span> <span class="token return-type class-name">Incident</span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerIncidentsSummary</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Pending <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Resolved <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Acknowledged <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Closed <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">List<span class="token punctuation">&lt;</span>IncidentInfo<span class="token punctuation">></span></span> Incidents <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Keeping information like that may seem redundant, but thanks to that, we can query the proper <em>CustomerIncidentsSummary</em> based on the linked incident. This should work both for document and relational databases.</p> <p>Of course, there’s a risk here. If we have many incidents per customer, the list may grow too big and impact performance. We can optimise that by, e.g. removing from the list closed incidents. But, of course, that’s something to consider.</p> <p>If we’re using a relational database, and plan to keep the incidents list in a dedicated table, then we need to remember not to reuse it between projections to not end up in the coupling and <em>rebuilt train</em> mentioned before.</p> <p>We should keep this list as an internal thing and technical implementation detail of this projection. We should also not return this data to the external world through the query.</p> <p><strong>Summing up.</strong> Decisions about adding a redundant property to the event should always be carefully made. The new property may not add more information but degrade it. We may lose a precious business context.</p> <p>That may be a valid tradeoff; we should at least <a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">note it</a>. Still, there are other options; I hope this article will be a good tutorial on techniques you may apply.</p> <p>As always, pick your poison!</p> <p><strong>Read also other article in Anti-patterns in event modelling series:</strong></p> <ul> <li><a href="/en/property-sourcing/">Property Sourcing</a>.</li> <li><a href="/en/state-obsession/">State Obsession</a>.</li> <li><a href="/en/clickbait_event/">Clickbait event</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/pl/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/pl/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Is the audit log a proper architecture driver for Event Sourcing?]]>https://event-driven.io/en/audit_log_event_sourcing/https://event-driven.io/en/audit_log_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5445ce2391a2dcfcd6a70dd7259f1d40/a331c/2023-05-20-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACnElEQVQozxXL608SAQAA8PsvWltbaQ1IEeQUB8mUI+E4wOPgeMjxkjvvhLvzERCPIaDAnA98tCKZkdilpU5eTtJqra312Fp9bP07rd/3H+AhPcFwMBxjorn57Hp6o1J6drhVP62edITOx/Pu53bn+k2jfXDeql10hbPGfrW2Wd5dXi7GH6ciAEERJBfiEpFEIbpSzpara/vHFeG83vzQaDdrrVbt4krovBOanXo6n8iW0gfC7m6lVFxNpjI8QMx4KZ6cS3OJQmyzUhTebgvH21+/Nz79uHzxdKW2V2pdHlxcCe3u4dpOcX2nUBd2y0+WM/loPMkCWgSGbShGOJzTRCrNtI9W3jc2fn153r561bzu1o/29l/vrW+m86V4cSu/VEpG03NUJOT2uZxuG6CCtJDJCGOo1eP0Bew8Y8/EfO3jpW8/u7///snlo2GWZCN+kibYeIRZpCmO9JM+A6JXjWkAtQ7SmRDEbsUIl5uw2e0G66QuyhOnJ9WTs5csGwiRLn6BotjgXIINP2IojnQFptQTWtEg+D9DJqPRhqJu3IRb9ZMIZNTrECNOuHGP02xHYcwyYTERpDcSm51dpBmO0mPmgfFRhUYDqCFIi8AGzOKj/aFI0E97aY4M0NOK0bF+UNmnGJaAShOOOfxTFEfSPOmnfBYUVoLSEaUcUGm14zCstyAM7eJZTyiIh0iPwzvVI1XIhoYkssGb9/ogM/LQguAEzi4wmcJSNBWdQPS9YgmgUD8ARzXDapVWN45aEYPZiDowp897u0/eK+7rEd2/0StB7DaaZ2k2nFvNJbNJl8dhdVjviPsBkQwUyUGZcsQA68yTBtikxzCj2YbeHQDFclA6NHxLLA3zM9wcw87PUuFprw/34HqzCRoAFf8AFRAIutuHb8oAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/5445ce2391a2dcfcd6a70dd7259f1d40/a331c/2023-05-20-cover.png" srcset="/static/5445ce2391a2dcfcd6a70dd7259f1d40/36ca5/2023-05-20-cover.png 200w, /static/5445ce2391a2dcfcd6a70dd7259f1d40/a3397/2023-05-20-cover.png 400w, /static/5445ce2391a2dcfcd6a70dd7259f1d40/a331c/2023-05-20-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Usually, one of the main drivers for Event Sourcing is the audit log capability.</strong> Indeed <a href="/en/relational_databases_are_event_stores/">event stores are append-only logs</a>, theoretically, we’re getting that for free. Yet, I’m usually not putting it as the front reason, and there are a few reasons for that.</p> <h2 id="building-a-proper-audit-log-is-not-so-simple-just-recording-the-result-of-operations-may-not-be-enough" style="position:relative;"><a href="#building-a-proper-audit-log-is-not-so-simple-just-recording-the-result-of-operations-may-not-be-enough" aria-label="building a proper audit log is not so simple just recording the result of operations may not be enough permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Building a proper audit log is not so simple. Just recording the result of operations may not be enough.</h2> <p>We also need to understand the context in which we recorded the fact. To get the full picture, we must also log the intention, so command. Thanks to that, we can verify if the correct event was logged. We also need to store the metadata like user id, permissions etc., to understand if the user had the rights to perform this action. Depending on our business case, we may need to store more information.</p> <p>All of that is contextual and need to be well thought out. We must design a proper compliance process if we’re in a regulated industry. It may also mean that we must prove that the data wasn’t changed after it was appended. In the more restrictive environment, that may even mean using <a href="https://en.wikipedia.org/wiki/Write_once_read_many">write-once-read-many (WORM) media</a>, as they only guarantee that what was written wasn’t ever updated.</p> <h2 id="we-can-do-that-also-without-using-event-sourcing" style="position:relative;"><a href="#we-can-do-that-also-without-using-event-sourcing" aria-label="we can do that also without using event sourcing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>We can do that also without using Event Sourcing.</h2> <p>We could just store events with state change and use them for audit needs and integrations using <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">outbox pattern</a>. That can even give us more advanced filtering, querying options etc.</p> <h2 id="filtering-capabilities-of-the-event-log-may-not-be-enough" style="position:relative;"><a href="#filtering-capabilities-of-the-event-log-may-not-be-enough" aria-label="filtering capabilities of the event log may not be enough permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Filtering capabilities of the event log may not be enough.</h2> <p><a href="https://martendb.io/events/querying.html">If you’re using Marten, you can query events</a>. That gives you the possibility to do data drilling and analytics. Yet, querying raw events may also not be performant enough.</p> <p>Most event stores are optimised for business logic needs, so accessing events by the stream identifier. That means the two types of operations: append to stream and read all events from the stream. Each event type may have a distinct set of properties, so event data has <a href="https://docs.honeycomb.io/concepts/high-cardinality/">high cardinality</a>.</p> <p>That means you need to try taming two different styles of usages. Rarely do event stores have such capabilities. You may need to publish events and store the results in a database that supports such filtering.</p> <h2 id="event-log-is-not-human-readable-on-its-own" style="position:relative;"><a href="#event-log-is-not-human-readable-on-its-own" aria-label="event log is not human readable on its own permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Event log is not human-readable on its own.</h2> <p>Sometimes audit need means that we need to display historical information to users. That also means we need to transform events into other structures. At least if we do not believe JSON is human-readable.</p> <p>I showed in <a href="/en/projections_in_marten_explained/">Event-driven projections in Marten explained</a> how to build projection to make projections to prepare data to be human-readable. You can use <a href="https://martendb.io/events/projections/event-projections.html">event projection</a> for that. It allows to transform events freely, e.g., creating a new read model record for each event.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentHistory</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Description <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IncidentHistoryTransformation</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">EventProjection</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> customerId<span class="token punctuation">,</span> contact<span class="token punctuation">,</span> description<span class="token punctuation">,</span> loggedBy<span class="token punctuation">,</span> loggedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"['</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">loggedAt</span><span class="token punctuation">}</span></span><span class="token string">'] Logged Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">' for customer '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">customerId</span><span class="token punctuation">}</span></span><span class="token string">' and description `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">description</span><span class="token punctuation">}</span></span><span class="token string">' through </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">contact</span><span class="token punctuation">}</span></span><span class="token string"> by '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">loggedBy</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>AgentAssignedToIncident<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> agentId<span class="token punctuation">,</span> assignedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">assignedAt</span><span class="token punctuation">}</span></span><span class="token string">] Assigned agent `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">agentId</span><span class="token punctuation">}</span></span><span class="token string"> to incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>IncidentResolved<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> resolution<span class="token punctuation">,</span> resolvedBy<span class="token punctuation">,</span> resolvedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">resolvedAt</span><span class="token punctuation">}</span></span><span class="token string">] Resolved Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">' with resolution `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">resolution</span><span class="token punctuation">}</span></span><span class="token string"> by '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">resolvedBy</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// (...) other events transformations</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="summing-up" style="position:relative;"><a href="#summing-up" aria-label="summing up permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summing up</h2> <p>All of that is something that you need to predict, plan and implement. That’s why I claim that Event Sourcing and event stores don’t provide full audit log capabilities out of the box. They make auditing easier, but if that’s the only driver for us, we should reevaluate our assumptions.</p> <p>The tricky part of advocating for Event Sourcing is that it doesn’t have a single killer feature.</p> <p><strong>What makes it unique is the multiple things that you’re getting out of the box, like:</strong></p> <ul> <li><a href="/en/how_to_effectively_compose_your_business_logic/">easier modelling of business process</a>,</li> <li><a href="/en/never_lose_data_with_event_sourcing/">not losing business data</a>,</li> <li><a href="/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/">extended diagnostics both technical and business</a>,</li> <li><a href="/en/projections_and_read_models_in_event_driven_architecture/">projections to interpret the same facts in multiple ways</a>.</li> </ul> <p><strong>Having the needs for those scenarios can be a driver to use Event Sourcing. Just audit needs may not be enough.</strong></p> <p>What are other drivers to not use Event Sourcing? Check my <a href="/en/when_not_to_use_event_sourcing/">dedicated article for that</a>.</p> <p>Event Sourcing is a useful and practical pattern. Yet, it’s not a silver bullet. If you want to use it, use it by making a proper architectural decision and understanding all the pros and cons.</p> <p>I prefer people jump on the Event Sourcing bandwagon because it’s helpful for them rather than <a href="/en/event_streaming_is_not_event_sourcing/">being blind by the hype and then failing</a>.</p> <p>I hope that this article helped you to clear some of them. You can also check my <a href="/en/training/">training page</a> if you’d like me to help you evaluate or deliver your business case.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[A few notes on running open source project after Marten v6 release]]>https://event-driven.io/en/a_few_notes_on_running_open_source_project/https://event-driven.io/en/a_few_notes_on_running_open_source_project/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/7b6c4e5853416a519857a8b718965e1a/a331c/2023-05-13-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACpUlEQVQozwXBaU+SAQAA4Pfn9KW2tmrp0nSFMB2mciii8HIIw4BQSAQU5UZAuVFe7vuSULkdvAh4NQUx1DlLN7c+9DN6HuD5qd69zBxmHI1C4He39OdX0bWpNkv5aTmbisTMoSeVTNrE4Cixf8Qq4BQgrXGeYqLgZQQMsRcBPD5Ub1uZczjy9/nkKGUOG1fLmUSrcXiai13Uy5BGJqWQdqQCPGLCp5LAAZ2FDbq/zerIOMLbT8BtJ9s5S/+EI+1GNOfVID+OwMWDej6R9JhzMSi8bcL1jZj4nCykjepXAnKBlUtNr9ENJCy9FwFc1uO5qHnfq02axY1dS887hEapL8adYiYh7TUFreqBlwM01HjWqQnppCvEKRefVtpgboJYCRoNXBzFqimT3yB2a5cCBsn7V4M8Mu04F4k5dS6deFPEAMdHCYNon1K4MEXC9SAjInpGSrVQsHbWDLDnUcUtorJf5VR+R/chF0FKtxltw+mgRakXMnVLjFXWJI/wRU0jo15/fvOiD9ePmhsaFuExDi4dUHFnLct0IW2aQ6RkQrZS0lrbsz9cFZMuvUHEEs3hNpYYbsOyT8GDhGwtm/Hh7RAKNSYgEcwsEDDJ+UGrzG1VV35Aj+2927N0Pay/aSbidgUfHNMLqI1de6fgPQ5vNUP6UsgUNcv33Jpa3FLzaYCHzsHTXfHfY+Ui72xVQ0chQ0YryKoXA+tsHXe2sqM6T5jL2/KTyBYMKY4D2oZPXd1ZL9ukcTkPaNdCV7XgzVmq6FjL21YPDMt+IUtDxKyApHrM1i4Hw2sLnHEceXiMPYHnYnEK6owcnFbP0zYW54H7buH+On9/nYMjRo9M4JDw5Ay6hvvVrZNVcuG7dqm6H8ymPHajNh2B4EIEzoUO9/3NSqJ1mvkPH6drNIA7HBkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/7b6c4e5853416a519857a8b718965e1a/a331c/2023-05-13-cover.png" srcset="/static/7b6c4e5853416a519857a8b718965e1a/36ca5/2023-05-13-cover.png 200w, /static/7b6c4e5853416a519857a8b718965e1a/a3397/2023-05-13-cover.png 400w, /static/7b6c4e5853416a519857a8b718965e1a/a331c/2023-05-13-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Last week we released <a href="https://github.com/JasperFx/marten/releases/tag/6.0.0">the next major Marten release</a>, and I’d like to share some of my lessons learned and insights with you. And by “you”, I don’t mean only you, my great reader, but also future me getting back to this article, as I’m blogging not to forget.</p> <h2 id="sometimes-postponing-things-is-fine" style="position:relative;"><a href="#sometimes-postponing-things-is-fine" aria-label="sometimes postponing things is fine permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Sometimes postponing things is fine</h2> <p>Initially, we wanted a quick release to align our codebase with .NET 7 and Npgsql 7 releases. Those are our <em><a href="https://en.wikipedia.org/wiki/Dependency_hell">diamond dependencies</a></em>. Even though .NET 7 upgrade should work by itself, breaking changes to Npgsql 7 and System.Text.Json are always giving us a hard time. Especially since people also use them explicitly in other places and not always can use <em>frozen</em> version from our code. We got the alpha release quickly to unblock people. It was the point when we could do a release. Yet, we didn’t.</p> <p>It appeared that most people who wanted to use the latest stuff were also fine with using the Alpha release. That shouldn’t be surprising if we take into account the typical technology adoption life cycle presented by <a href="https://www.goodreads.com/book/show/61329.Crossing_the_Chasm">Geoffrey A. Moore in Crossing the Chasm</a> book.</p> <p>People immediately upgrading the tooling are also more open to being early adopters and face some challenges, but have the benefit of being first and iterating quickly.</p> <p>That’s why we decided to make a few more changes and cleanup to make our codebase for the new challenges, e.g. <a href="https://jeremydmiller.com/2023/05/11/marten-v6-is-out-and-the-road-to-wolverine-1-0/">the upcoming Wolverine 1 release</a>.</p> <h2 id="dont-be-sainter-than-the-pope" style="position:relative;"><a href="#dont-be-sainter-than-the-pope" aria-label="dont be sainter than the pope permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Don’t be <em>sainter</em> than the pope</h2> <p>Open Source is something between passion, madness, philanthropy and duty. We’re non-paid labour; we don’t charge for licences etc. Of course, we’d like to build a sustainable model around our work, but that’s not easy.</p> <p>In Marten, we’re strict around semantic versioning because we believe that’s a basis for building trust. We try to avoid breaking changes, but when we do, we always try to make them explicit.</p> <p>Still, avoiding breaking changes at all costs is not the best idea. Especially if they’re only technically breaking. In recent years, .NET team has been iterating pretty fast, encouraging users to migrate .NET version to the latest tooling. They defined <a href="https://dotnet.microsoft.com/en-us/platform/support/policy">the Official .NET Support Policy</a>.</p> <p>We could try to support older versions of .NET, but if Microsoft is not doing that, why should we make our non-paid live harder?</p> <p>Of course, the decision is not easy, as we don’t want to let down our users, but we also want the majority to get more features faster and with better quality. Yet, if someone cannot upgrade immediately, that’s fine. They can continue to use older versions.</p> <h2 id="release-notes-and-smooth-migration-path-are-important" style="position:relative;"><a href="#release-notes-and-smooth-migration-path-are-important" aria-label="release notes and smooth migration path are important permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Release notes and smooth migration path are important</h2> <p>Open Source work is not only about playing with technology. It’s about working with the community. You also need to do stuff that’s tedious but, in the end, helpful for the community. For instance: release notes.</p> <p>I spend several hours working on <a href="https://github.com/JasperFx/marten/releases/tag/6.0.0">the Marten v6 release notes updates</a>. This time I grouped them by the area and type of change (breaking, new, changed) and added more context.</p> <p>GitHub autogeneration is helpful, but I don’t feel that’s enough to give the usability context to the people. It’s still focused on the PRs and issues, which usually give context to the people knowing the changing scope, but not for the whole community.</p> <p>I believe that preparing proper release notes is a must-have for building mutual trust between maintainers and users. On showing that “you care about them”. Those are small things that can create a snowball effect.</p> <p>Also, it’s useful for maintainers, as to not have the need to support all the past versions, we should encourage people to use the most recent one. It makes maintenance easier.</p> <p>Of course, release notes are not all; that stuff should also get into docs. That’s why we also have a dedicated section in our docs for <a href="https://martendb.io/migration-guide.html">the migration guide</a>.</p> <p>We also try to do a two-step removal. At first, we’re marking old features as obsolete together with the information on how to migrate. Thanks to that, people can see that in their IDE. Then in the next major release, we’re removing old stuff. That gives people a graceful period to perform the migration with a smooth transition.</p> <p>Of course, we try to avoid breaking stuff where we can.</p> <h3 id="having-a-team-is-crucial" style="position:relative;"><a href="#having-a-team-is-crucial" aria-label="having a team is crucial permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Having a team is crucial</h3> <p>We’ve been working with Jeremy and Babu for a few years together. During that time, we had spans where life got into our way, and we were less active. You can see that clearly in the <a href="https://github.com/JasperFx/marten/graphs/contributors">GitHub contributors chart</a>.</p> <p>Having a team allowed us to avoid being hit by the bus factor. And that also helped us to spread the work.</p> <p>This time I was driving the v6 release, which let Jeremy focus on Wolverine and Babu to improve our docs.</p> <p>If you’re planning to build an open source project, think in advance about how to encourage others to contribute because…</p> <h3 id="building-community-is-also-critical" style="position:relative;"><a href="#building-community-is-also-critical" aria-label="building community is also critical permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Building community is also critical</h3> <p><a href="https://event-driven.io/en/how_to_start_with_open_source/">I started as a Marten community member before joining the core team</a>. And that’s an example of how vital community can impact the project.</p> <p>Want more? In this release, we had 12 external contributors, and we have 155 in general. That’s a lot!</p> <p>We’re pretty lucky that we have such an open and vital community. We extremely rarely get snarky or toxic comments. It might be that we’re still in a niche, but I prefer to think that our work with the community just pays off.</p> <p>Our maxim is <em>“We take pull requests!”</em> and we encourage people to contribute and try to make that accessible.</p> <p>What was staggering when I read <a href="https://seankilleen.com/2022/06/announcing-net-oss-survey-results/">Sean Kileen’s .NET OSS Maintainer Support Survey results</a> was that most of the maintainers don’t care about building community. What’s more, they don’t even want to do it.</p> <p>That’s fine, as long as you want to just play with code, but if you want your project to be adopted, you must invest your time building relationships with people.</p> <p>Recently we also migrated to <a href="https://discord.gg/WMxrvegf8H">Discord</a> from Gitter. We were afraid to do that, but we were forced by <a href="https://blog.gitter.im/2023/02/13/gitter-has-fully-migrated-to-matrix/">unexpected merge with Matrix</a>. And that was a great decision!</p> <p>Our community was already active on Gitter, and we try to help there as much as possible, but it’s even more active on Discord. Which can be, of course, overwhelming sometimes, but we’re super happy about that. It’s also motivating to see all those people (at the time of writing, almost 500) interested in our product, sharing their feedback and helping us to see the real usage.</p> <p>Using public groups and channels is important in the beginning to reach people, but building your own channels is also essential. Not having them will eventually become limiting. Public groups have their own rules. Usually, you cannot promote your work and ask for feedback explicitly. Also, some users want to avoid joining big, general communities when they’re only interested in your project. They don’t want to be overwhelmed or interact with all the others if they want to interact with your team explicitly.</p> <h3 id="trial-the-features" style="position:relative;"><a href="#trial-the-features" aria-label="trial the features permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Trial the features</h3> <p>Together with Jeremy, we’re quite active also in blogging about Marten (check the <a href="https://jeremydmiller.com/">great Jeremy’s blog</a>). It gives the possibility to share more context to users but also trials new stuff and usages.</p> <p>My article about <a href="/en/integrating_Marten/">integrating Marten with other tools</a> is a decent example of that. I wrote it to explain how to do it using current features, but we noticed that people were referring to it, applying this pattern and linking it to each other.</p> <p>That gave us the certainty that this feature is needed and that the proposed solution matches people’s expectations.</p> <p>Based on that, we’re planning to deliver a proper, formal and optimised Subscription mechanism.</p> <p>The other idea is to get feedback from people and not be limited by the <a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">breaking changes policy</a> is to introduce ideas as the <em>experimental</em>, for instance, add registration like <em>options.Projections.Experimental.UseSomeIdea()</em>. That should make clear to users that it’s experimental stuff suited for the feedback from the early adopters.</p> <h3 id="be-transparent-with-users" style="position:relative;"><a href="#be-transparent-with-users" aria-label="be transparent with users permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Be transparent with users</h3> <p>All of that has to be communicated transparently with users. Best if you try to use different channels, as you should expect that only some people are active on certain communication channels. Most people are silent if all is working.</p> <p>If you’re clear and proactive, it’s much easier to build trust and get feedback.</p> <p><strong>I hope those insights were interesting to you.</strong> OSS <em>work</em> is a long-term game.</p> <p>If you’d like to start your own project, check: <a href="/en/how_to_start_with_open_source/">How to get started with Open Source?</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[General strategy for migrating relational data to document-based]]>https://event-driven.io/en/strategy_on_migrating_relational_data_to_document_based/https://event-driven.io/en/strategy_on_migrating_relational_data_to_document_based/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/80dce87bccae167764b303f4277f616d/a331c/2023-05-07-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACN0lEQVQoz3XRS08TURwF8NEv4cqlfgPdqTuJbt1oYoxGEx9IogYkBjBGkYXGFwZRTGqAoEGwKJoiJlTe0rS2oEJbWlpqH8yj0+nM/O9j7p3pNRg3Rjn7X85JjgRbxgaoAqgI5TCJ1eio8J64bit36133Iuc3HGdA2oKZADpCJYwThM4CHZ4iIwG506YNonae82vM6SZ0QvqfNDYL8Tohi4R+Ik5fjXXERNfuSOhQoNvy2hh7QOh7TJalv5kFUEFoA+MUISFCRx3Ww1m7wxuFOPNOa9sxMXgyNWY6fkoigIrSP1OLmKwQOkupn7Fuzts976rD6oW4PJk+duDp3rWKjzoLgLIA+h9s2aZl6zYUEFrBZIbgNw57zt07nttq2A2h1ROUNq6ozW+XWoQ3DShpIw3A3MQI7Jprulx1vTVeC3u1cSb81O6i9m0hOr5kzwVix4W4i/Ez7oxj/ANg4/cRtgQIAVg90z9P+5aDH5byk5+T4RHtxRX1Qh3R70HgyNL1XT7/Qeb2UjaG6TdMSoBNhBHCWIJqeUOW9/WtbT8aa2qKFFomh3fuj0rb5LOHkdurv6rL10nJh3t0rd+Sx41iuFKIV/KpSiFjlHKSXkhr68lgan0gmAjNLcD8hPL4Vv7+JTX4qJzrN6aalcFT5ZmbemZYS3zUEnNKPKTEI+rqopZZlgw5pxez0WQ2GE3Howkl9j0Xni9PDVVmXpvKvJ1+Wf3aWcoMGcpiVUkZSq6qFk1NNnXVMsq/AOxcKvmmGjtYAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/80dce87bccae167764b303f4277f616d/a331c/2023-05-07-cover.png" srcset="/static/80dce87bccae167764b303f4277f616d/36ca5/2023-05-07-cover.png 200w, /static/80dce87bccae167764b303f4277f616d/a3397/2023-05-07-cover.png 400w, /static/80dce87bccae167764b303f4277f616d/a331c/2023-05-07-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I was recently asked how to migrate a project using relational data to a document-based approach (e.g. from .NET ORM <a href="https://learn.microsoft.com/en-gb/ef/">Entity Framework</a> into <a href="https://martendb.io/">Marten</a>).</strong> As always, it’s easy to ask a question but much harder to answer. But hell, let’s try!</p> <p>Document approach differs from relational mainly by:</p> <ul> <li>using weak schema,</li> <li>having denormalised data.</li> </ul> <p>Contrary to common belief, document data is structured but less rigidly, as in the relational approach. 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. That’s why probably it’s called <em>weak schema</em>. In my opinion, that’s not a weakness by definition. In fact, it may mean that we <a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">care about ourselves and apply backwards and forward compatibility</a>.</p> <p><strong>Document databases work best if we access them by id, as they’re <a href="/en/key-value-stores/">type of key-value databases</a>.</strong> That means we should group related data and keep them together to use it efficiently. For instance, selected product items only make sense with the shopping cart. That’s why we should keep them together, use a shopping cart as a root document, and keep product items as nested collections.</p> <p><strong>Documents may also have relations.</strong> But also lightweight, without enforcement like relational databases with their <a href="https://www.w3schools.com/sql/sql_foreignkey.asp">foreign keys</a>. Keeping the shopping cart example, we may have products’ definitions as separate documents and keep only their ids in the product items list. We may also take the tradeoff and keep basic information like names to reduce the need to access other documents. Document databases usually don’t provide efficient <a href="https://www.w3schools.com/sql/sql_join.asp">joins like relational databases</a>. They typically do look-ups. Look-up means that you’re querying first one set of documents, then using data from them <a href="https://martendb.io/documents/querying/linq/include.html">getting related documents</a>.</p> <p><strong>So, how to migrate relational data into document-based?</strong> The simplistic answer is to migrate data by loading batches from the relational database, serialising them and <a href="https://martendb.io/documents/storing.html#bulk-loading">storing them in batches in the document database</a>. But that’s the technical recommendation. Moving from a relational way to the document one requires more than that, e.g. denormalising data and finding a way to break the strong relationships.</p> <p>My general strategy would be:</p> <ol> <li> <p>Find root entities (e.g. for a shopping cart with product items, the shopping cart is the root entity).</p> </li> <li> <p>Then check related properties to see if they can live independently.</p> </li> <li> <p>If they can’t live separately, embed them inside the root entity. If they can, reference them by id.</p> </li> <li> <p>Set those entities that can live on their own as documents.</p> </li> <li> <p>Do this exercise and define policies for each table. Prepare the classes that will be loading and storing the data.</p> </li> <li> <p>Set up a project to load data from a relational database and store them in a document database. The import process may take some time, so it should be run by a background worker. Remember to put extensive logging; this will help you troubleshoot issues.</p> </li> <li> <p>Do a dry run and test the first migration for a single table.</p> </li> <li> <p>After that, create a program that will load entities from relational and document databases and compare if they’re the same.</p> </li> <li> <p>Check what type of <a href="https://martendb.io/documents/indexing/">indexes your document database provides</a> and apply those that make sense.</p> </li> <li> <p>Rinse/repeat for other data.</p> </li> <li> <p>Consider also using the <em><a href="https://shopify.engineering/refactoring-legacy-code-strangler-fig-pattern">strangler fig pattern</a></em> while migrating to your existing system to do it step by step.</p> </li> </ol> <p><strong>Of course, you’ll need to figure out a lot of grey area, but it’d be worth first <a href="https://www.youtube.com/watch?v=3gib0hKYjB0">making the change easy, then making the easy change</a>.</strong> So, minimising the data model refactoring during migration.</p> <p>It’s also better to start with simple mapping without changing schema too much(unless your data is simple). After it works, consider adjusting it to fit the document approach. Read also my other article where I lined up the heuristics on changing legacy design: <a href="/en/chesterton_fence_and_software_architecture">What do the British writer and his fence have to do with Software Architecture?</a>.</p> <p><strong>See also decent reference guides:</strong></p> <ul> <li><a href="https://hevodata.com/learn/relational-database-to-mongodb/">HevoData - How to Migrate Relational Database to MongoDB?: Made Easy</a></li> <li><a href="https://docs.couchbase.com/server/current/install/migrate-mysql.html">Couchbase - Migrating from Relational Databases</a>,</li> <li><a href="https://learn.mongodb.com/courses/m320-mongodb-data-modeling">MongoDB Data Modeling</a>.</li> </ul> <p>Of course, those are just simple heuristics, as each migration should be made case by case, respecting the data we have and their business context.</p> <p>Still, I hope that they will be helpful enough to start your journey and plan your migration strategy!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to test event-driven projections]]>https://event-driven.io/en/testing_event_driven_projections/https://event-driven.io/en/testing_event_driven_projections/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e28ca783c9c1e46f71330bbb02c632ef/a331c/2023-04-29-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AKrM3+Pa2Ojn6KzQ48fj89zv+uHz+/n//3iTpVyEnf/++L7S3pnR68bu/77p/MTo+PT6/cXs/KDf+Z3d+QC51eXv6unZ2dyixtuVutajy+WKwuBojKMAID+Ao7f28fDT3uajzePe8fzU6/jX7vrh9P7Y7fmn3fab3fkApMXY////8PH1udDg4Ofs+vr8f7bXADpkAAUfK1Jw2eTsr9HjeMDli83wjM3ujsvsqNjzltTymtv5nt76AMLc6sfh78zl8+P2/+Dr8cPL0+35/yV6qEp7lluIooC3043O67nl+cbr+6Li/Jrb+JjY9p7e+Z3b9ZvZ9QAibZimxtmuz+Xx//+uv8vDztj///9hpMmSxd7i9//Y8v7b8fze8Pru+P6+5fiZ2PSd3Pec4v+Z2fSV1PAAQYy1/////+3g+evjmqe18fX3////hbrXADdflqq5qdbtk9Lzpdn0qtrzn935kMfgmdfyq8XMppuRtNzsADd/p6Czw7/U37q4uK2fn//////8+oCz0AFIc76+w567zHq62ITA35bX9Z/f+57c9sbW1tu8pbt9WsenjgA4dp8ISXasyNi+3OuCobbt7/L///+JvtoALVgDGzgEI0AAGzt/mKqx3/SL2fy+19vJxsfdzsDwwqOfrbgARX+mGVyH7evr1uHoKl19NVh0SmqDF0ltHFl/UoWjED9iCitJfZirx+Tzwej6yeLtorHcvbnQxb/RmbLoAFiMrgdKd8HY5K/K2QAuVAEpTQAkSAU7Y8PZ5P///42+2IG/4JXO7KHX8qbZ86PZ9ZXM75+266C065676wB9nbMAMVkXTnMTRWgALVAGNFcAJ0syao32+Pf+7+jH4O54wuSj5f+l2vPM7Pqg4Pqb2viftuelt+ues+b6t9Yn25pJhQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e28ca783c9c1e46f71330bbb02c632ef/a331c/2023-04-29-cover.png" srcset="/static/e28ca783c9c1e46f71330bbb02c632ef/36ca5/2023-04-29-cover.png 200w, /static/e28ca783c9c1e46f71330bbb02c632ef/a3397/2023-04-29-cover.png 400w, /static/e28ca783c9c1e46f71330bbb02c632ef/a331c/2023-04-29-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="/en/projections_and_read_models_in_event_driven_architecture/">Projections in an event-driven world</a> are a way to interpret registered events.</strong> We can take a sequence of events and build from them read models in a predictable way. They’re predictable so that they’ll always generate the same result for the same logic and sequence of events. Still, it’s never so easy regarding real usage and our colourful life.</p> <p>We could write projections so that they’re always upserting the whole data. It could work in many cases, but if we want to make them performant and optimised, then we should benefit from the end storage capabilities. For instance, MongoDB provides a neat API for atomic partial updates. Why wouldn’t we benefit from them? Yet, we also need to embrace that not all databases are giving us the guarantee that we’ll immediately read our writes (e.g. MongoDB doesn’t). Moreover, event-driven tools <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">give us at-least-once delivery guarantee</a>, meaning we might be processing events multiple times. I explained some challenges in <a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">Dealing with Eventual Consistency and Idempotency in MongoDB projections</a>.</p> <p>So projection logic is predictable, but life, with its asynchronous nature, is difficult. We cannot blindly assume that our predictable logic will survive real-world chaos. We need to verify that.</p> <p><strong>In software development, by verify, we usually mean testing, and that’s what we will do today!</strong> We’ll use integration tests for that, as unit tests won’t give us enough trust in the test results for the reasons I explained above.</p> <p><strong>Let’s say we’re handling the simplified shopping cart scenario, known from my other articles.</strong> We’re registering facts about when it was opened, confirmed or cancelled and which products were added or removed from it. If we’re handling it in Node.js, EventStoreDB and MongoDB, the projection code could look like that.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartDetails</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> status<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> PricedProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> canceledAt<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> revision<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> ShoppingCartStatus <span class="token operator">=</span> <span class="token punctuation">{</span> Pending<span class="token operator">:</span> <span class="token string">'Pending'</span><span class="token punctuation">,</span> Canceled<span class="token operator">:</span> <span class="token string">'Canceled'</span><span class="token punctuation">,</span> Confirmed<span class="token operator">:</span> <span class="token string">'Confirmed'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">getShoppingCartsCollection</span> <span class="token operator">=</span> <span class="token punctuation">(</span>mongo<span class="token operator">:</span> MongoClient<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token generic-function"><span class="token function">getMongoCollection</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartDetails<span class="token operator">></span></span></span><span class="token punctuation">(</span>mongo<span class="token punctuation">,</span> <span class="token string">'shoppingCartDetails'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> project <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> carts<span class="token operator">:</span> Collection<span class="token operator">&lt;</span>ShoppingCartDetails<span class="token operator">></span><span class="token punctuation">,</span> event<span class="token operator">:</span> ShoppingCartEvent<span class="token punctuation">,</span> streamRevision<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>UpdateResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> expectedRevision <span class="token operator">=</span> streamRevision <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartOpened'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $setOnInsert<span class="token operator">:</span> <span class="token punctuation">{</span> clientId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>clientId<span class="token punctuation">,</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> openedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>openedAt<span class="token punctuation">,</span> revision<span class="token operator">:</span> streamRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string-property property">'productItems.productId'</span><span class="token operator">:</span> <span class="token punctuation">{</span> $ne<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token string-property property">'productItems.price'</span><span class="token operator">:</span> <span class="token punctuation">{</span> $ne<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>price <span class="token punctuation">}</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> expectedRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $addToSet<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> price<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> expectedRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'productItems.$[productItem].quantity'</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> arrayFilters<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token string-property property">'productItem.productId'</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> <span class="token string-property property">'productItem.price'</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string-property property">'productItems.productId'</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> revision<span class="token operator">:</span> expectedRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'productItems.$.quantity'</span><span class="token operator">:</span> <span class="token operator">-</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> expectedRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>confirmedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> revision<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartCanceled'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> expectedRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">,</span> canceledAt<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>canceledAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> revision<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> _<span class="token operator">:</span> <span class="token builtin">never</span> <span class="token operator">=</span> event<span class="token punctuation">;</span> <span class="token keyword">throw</span> ShoppingCartErrors<span class="token punctuation">.</span><span class="token constant">UNKNOWN_EVENT_TYPE</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">projectToShoppingCartDetails</span> <span class="token operator">=</span> <span class="token punctuation">(</span>mongo<span class="token operator">:</span> MongoClient<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>resolvedEvent<span class="token operator">:</span> SubscriptionResolvedEvent<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>UpdateResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> resolvedEvent<span class="token punctuation">.</span>event <span class="token operator">===</span> <span class="token keyword">undefined</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">isCashierShoppingCartEvent</span><span class="token punctuation">(</span>resolvedEvent<span class="token punctuation">.</span>event<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token function">resolve</span><span class="token punctuation">(</span>EmptyUpdateResult<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> event <span class="token punctuation">}</span> <span class="token operator">=</span> resolvedEvent<span class="token punctuation">;</span> <span class="token keyword">const</span> streamRevision <span class="token operator">=</span> <span class="token function">Number</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>revision<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> <span class="token function">getShoppingCartsCollection</span><span class="token punctuation">(</span>mongo<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">project</span><span class="token punctuation">(</span>shoppingCarts<span class="token punctuation">,</span> event<span class="token punctuation">,</span> streamRevision<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’re explicitly applying the changes from events and building the shopping cart details read model using MongoDB API. We’re also handling eventual consistency and idempotency as explained <a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">in another article</a>.</p> <p><strong>I explained in <a href="/en/testing_event_sourcing/">Testing business logic in Event Sourcing, and beyond!</a> that the best way to test event-driven software is to assume that the source of truth comes from events. That means:</strong></p> <ol> <li><strong>Given</strong> a sequence of events.</li> <li><strong>When</strong> logic was run.</li> <li><strong>Then</strong> we got a new result or failure.</li> </ol> <p>This pattern will also apply to our case, as read models are always built from events; we’ll call the predictable projection logic and get the expected read model state as a result or no change if the event was already handled.</p> <p>Our tests could look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'Shopping Cart details'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> mongo<span class="token operator">:</span> MongoClient<span class="token punctuation">;</span> <span class="token keyword">let</span> given<span class="token operator">:</span> Spec<span class="token operator">&lt;</span>ShoppingCartEvent<span class="token punctuation">,</span> ShoppingCartDetails<span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">beforeAll</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> mongoContainer <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">new</span> <span class="token class-name">MongoDBContainer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>mongoContainer<span class="token punctuation">.</span><span class="token function">getConnectionString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> mongo <span class="token operator">=</span> mongoContainer<span class="token punctuation">.</span><span class="token function">getClient</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> mongo<span class="token punctuation">.</span><span class="token function">connect</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> given <span class="token operator">=</span> Spec<span class="token punctuation">.</span><span class="token function">for</span><span class="token punctuation">(</span> <span class="token function">getShoppingCartsCollection</span><span class="token punctuation">(</span>mongo<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">projectToShoppingCartDetails</span><span class="token punctuation">(</span>mongo<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">afterAll</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>mongo<span class="token punctuation">)</span> <span class="token keyword">await</span> mongo<span class="token punctuation">.</span><span class="token function">close</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'On ShoppingCartOpened event'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should set up an empty shopping cart'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> clientId <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> openedAt <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> openedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> clientId<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> openedAt<span class="token punctuation">,</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should be idempotent if run twice'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> clientId <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> openedAt <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCartOpened<span class="token operator">:</span> ShoppingCartEvent <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> openedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">given</span><span class="token punctuation">(</span>shoppingCartOpened<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> event<span class="token operator">:</span> shoppingCartOpened<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">0n</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">thenNotUpdated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'On ProductItemAddedToShoppingCart event'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should add product item to items list'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> clientId <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> openedAt <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productItem<span class="token operator">:</span> PricedProductItem <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> price<span class="token operator">:</span> <span class="token number">123</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token function">opened</span><span class="token punctuation">(</span><span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> openedAt <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> clientId<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> openedAt<span class="token punctuation">,</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span>productItem<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should be idempotent if run twice'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productItemAdded<span class="token operator">:</span> ShoppingCartEvent <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> price<span class="token operator">:</span> <span class="token number">123</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token function">opened</span><span class="token punctuation">(</span><span class="token punctuation">{</span> shoppingCartId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> productItemAdded<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> event<span class="token operator">:</span> productItemAdded<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">1n</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">thenNotUpdated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">describe</span><span class="token punctuation">(</span><span class="token string">'On ProductItemRemovedFromShoppingCart event'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should decrease existing product item quantity'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> clientId <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> openedAt <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productId <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> price <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">;</span> <span class="token keyword">const</span> initialQuantity <span class="token operator">=</span> <span class="token number">20</span><span class="token punctuation">;</span> <span class="token keyword">const</span> removedQuantity <span class="token operator">=</span> <span class="token number">9</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">opened</span><span class="token punctuation">(</span><span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> openedAt <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">productItemAdded</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> initialQuantity<span class="token punctuation">,</span> price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> removedQuantity<span class="token punctuation">,</span> price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> clientId<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> openedAt<span class="token punctuation">,</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> initialQuantity <span class="token operator">-</span> removedQuantity<span class="token punctuation">,</span> price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">it</span><span class="token punctuation">(</span><span class="token string">'should be idempotent if run twice'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productId <span class="token operator">=</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> price <span class="token operator">=</span> <span class="token number">123</span><span class="token punctuation">;</span> <span class="token keyword">const</span> initialQuantity <span class="token operator">=</span> <span class="token number">20</span><span class="token punctuation">;</span> <span class="token keyword">const</span> removedQuantity <span class="token operator">=</span> <span class="token number">9</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productItemRemoved<span class="token operator">:</span> ShoppingCartEvent <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> removedQuantity<span class="token punctuation">,</span> price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">opened</span><span class="token punctuation">(</span><span class="token punctuation">{</span> shoppingCartId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">productItemAdded</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> initialQuantity<span class="token punctuation">,</span> price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> productItemRemoved <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> event<span class="token operator">:</span> productItemRemoved<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">2n</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">thenNotUpdated</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> opened <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> openedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartEvent <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> shoppingCartId <span class="token operator">??</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> clientId<span class="token operator">:</span> clientId <span class="token operator">??</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> openedAt<span class="token operator">:</span> openedAt <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> productItemAdded <span class="token operator">=</span> <span class="token punctuation">(</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> productItem<span class="token operator">:</span> PricedProductItem <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartEvent <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>You for sure noticed the pattern:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"> <span class="token keyword">await</span> <span class="token function">given</span><span class="token punctuation">(</span> <span class="token function">opened</span><span class="token punctuation">(</span><span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> openedAt <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">productItemAdded</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> initialQuantity<span class="token punctuation">,</span> price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> removedQuantity<span class="token punctuation">,</span> price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> clientId<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> openedAt<span class="token punctuation">,</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> initialQuantity <span class="token operator">-</span> removedQuantity<span class="token punctuation">,</span> price<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>And maybe thinking about what testing framework gives me such syntax. The answer is none; I just wrote it on my own. How?</strong></p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">HasRevisionOrPosition</span> <span class="token operator">=</span> <span class="token punctuation">{</span> revision<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> position<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token operator">|</span> Long <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">DocumentWithRevisionOrPosition</span> <span class="token operator">=</span> Document <span class="token operator">&amp;</span> HasRevisionOrPosition<span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">EventWithMetadata<span class="token operator">&lt;</span><span class="token constant">E</span><span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> event<span class="token operator">:</span> <span class="token constant">E</span><span class="token punctuation">;</span> revision<span class="token operator">?</span><span class="token operator">:</span> bigint<span class="token punctuation">;</span> position<span class="token operator">?</span><span class="token operator">:</span> bigint <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Spec<span class="token operator">&lt;</span> <span class="token constant">E</span> <span class="token keyword">extends</span> Event<span class="token punctuation">,</span> Doc <span class="token keyword">extends</span> DocumentWithRevisionOrPosition <span class="token operator">=</span> DocumentWithRevisionOrPosition <span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token operator">...</span>givenEvents<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token constant">E</span> <span class="token operator">|</span> EventWithMetadata<span class="token operator">&lt;</span><span class="token constant">E</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function-variable function">when</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>events<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token constant">E</span> <span class="token operator">|</span> EventWithMetadata<span class="token operator">&lt;</span><span class="token constant">E</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function-variable function">then</span><span class="token operator">:</span> <span class="token punctuation">(</span>id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> expected<span class="token operator">:</span> Doc<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function-variable function">thenUpdated</span><span class="token operator">:</span> <span class="token punctuation">(</span>times<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function-variable function">thenNotUpdated</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> Spec <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token keyword">for</span><span class="token operator">:</span> <span class="token operator">&lt;</span> <span class="token constant">E</span> <span class="token keyword">extends</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> Doc <span class="token keyword">extends</span> <span class="token class-name">DocumentWithRevisionOrPosition</span> <span class="token operator">=</span> DocumentWithRevisionOrPosition <span class="token operator">></span><span class="token punctuation">(</span> collection<span class="token operator">:</span> Collection<span class="token operator">&lt;</span>Doc<span class="token operator">></span><span class="token punctuation">,</span> <span class="token function-variable function">project</span><span class="token operator">:</span> <span class="token punctuation">(</span>event<span class="token operator">:</span> SubscriptionResolvedEvent<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>UpdateResult<span class="token operator">></span> <span class="token punctuation">)</span><span class="token operator">:</span> Spec<span class="token operator">&lt;</span><span class="token constant">E</span><span class="token punctuation">,</span> Doc<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token operator">...</span>givenEvents<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token constant">E</span> <span class="token operator">|</span> EventWithMetadata<span class="token operator">&lt;</span><span class="token constant">E</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token function-variable function">when</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token operator">...</span>events<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token constant">E</span> <span class="token operator">|</span> EventWithMetadata<span class="token operator">&lt;</span><span class="token constant">E</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> allEvents <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token operator">...</span>givenEvents<span class="token punctuation">,</span> <span class="token operator">...</span>events<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">run</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> position <span class="token operator">=</span> <span class="token number">0n</span><span class="token punctuation">;</span> <span class="token keyword">let</span> changesCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">let</span> acknowledgementCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> event <span class="token keyword">of</span> allEvents<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> options <span class="token operator">=</span> <span class="token punctuation">{</span> position<span class="token operator">:</span> <span class="token string">'position'</span> <span class="token keyword">in</span> event <span class="token operator">&amp;&amp;</span> <span class="token keyword">typeof</span> event<span class="token punctuation">.</span>position <span class="token operator">===</span> <span class="token string">'bigint'</span> <span class="token operator">?</span> event<span class="token punctuation">.</span>position <span class="token operator">:</span> position<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token string">'revision'</span> <span class="token keyword">in</span> event <span class="token operator">&amp;&amp;</span> <span class="token keyword">typeof</span> event<span class="token punctuation">.</span>revision <span class="token operator">===</span> <span class="token string">'bigint'</span> <span class="token operator">?</span> event<span class="token punctuation">.</span>revision <span class="token operator">:</span> position<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> projectedEvent <span class="token operator">=</span> <span class="token function">toSubscriptionEvent</span><span class="token punctuation">(</span> <span class="token string">'event'</span> <span class="token keyword">in</span> event <span class="token operator">?</span> event<span class="token punctuation">.</span>event <span class="token operator">:</span> event<span class="token punctuation">,</span> options <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">project</span><span class="token punctuation">(</span>projectedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span> changesCount <span class="token operator">+=</span> result<span class="token punctuation">.</span>upsertedCount <span class="token operator">+</span> result<span class="token punctuation">.</span>modifiedCount<span class="token punctuation">;</span> acknowledgementCount <span class="token operator">+=</span> result<span class="token punctuation">.</span>acknowledged <span class="token operator">?</span> <span class="token number">1</span> <span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">;</span> position<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">expect</span><span class="token punctuation">(</span>acknowledgementCount<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span>allEvents<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> changesCount <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> thenUpdated <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>times<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> changesCount <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">expect</span><span class="token punctuation">(</span>changesCount <span class="token operator">-</span> givenEvents<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toBe</span><span class="token punctuation">(</span>times<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> thenUpdated<span class="token punctuation">,</span> <span class="token function-variable function">thenNotUpdated</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">thenUpdated</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token function-variable function">then</span><span class="token operator">:</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> expected<span class="token operator">:</span> Doc<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">thenUpdated</span><span class="token punctuation">(</span>events<span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">assertUpdated</span><span class="token punctuation">(</span>collection<span class="token punctuation">,</span> id<span class="token punctuation">,</span> expected<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> assertUpdated <span class="token operator">=</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span>Doc <span class="token keyword">extends</span> DocumentWithRevisionOrPosition<span class="token operator">></span></span></span><span class="token punctuation">(</span> collection<span class="token operator">:</span> Collection<span class="token operator">&lt;</span>Doc<span class="token operator">></span><span class="token punctuation">,</span> id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> expected<span class="token operator">:</span> Doc <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> objectId <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Yes, MongoDB typings are far from perfect...</span> <span class="token keyword">const</span> filter <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token comment">// filter by id</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>objectId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// ensure that we got document at expected revision or position</span> <span class="token operator">...</span><span class="token punctuation">(</span><span class="token string">'revision'</span> <span class="token keyword">in</span> expected <span class="token operator">?</span> <span class="token punctuation">{</span> revision<span class="token operator">:</span> expected<span class="token punctuation">.</span>revision <span class="token keyword">as</span> <span class="token builtin">number</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">...</span><span class="token punctuation">(</span><span class="token string">'position'</span> <span class="token keyword">in</span> expected <span class="token operator">?</span> <span class="token punctuation">{</span> position<span class="token operator">:</span> Long<span class="token punctuation">.</span><span class="token function">fromBigInt</span><span class="token punctuation">(</span>expected<span class="token punctuation">.</span>position <span class="token keyword">as</span> bigint<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token operator">:</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">as</span> <span class="token builtin">unknown</span> <span class="token keyword">as</span> Filter<span class="token operator">&lt;</span>Doc<span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">const</span> item <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">retryIfNotFound</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> collection<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span>filter<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">expect</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toStrictEqual</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token operator">...</span>expected<span class="token punctuation">,</span> _id<span class="token operator">:</span> objectId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> toSubscriptionEvent <span class="token operator">=</span> <span class="token operator">&lt;</span><span class="token constant">E</span> <span class="token keyword">extends</span> <span class="token class-name">Event</span><span class="token operator">></span><span class="token punctuation">(</span> event<span class="token operator">:</span> <span class="token constant">E</span><span class="token punctuation">,</span> options<span class="token operator">:</span> <span class="token punctuation">{</span> position<span class="token operator">:</span> bigint<span class="token punctuation">;</span> revision<span class="token operator">:</span> bigint <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token operator">:</span> SubscriptionResolvedEvent <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> subscriptionId<span class="token operator">:</span> <span class="token function">mongoObjectId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>event<span class="token punctuation">,</span> revision<span class="token operator">:</span> options<span class="token punctuation">.</span>revision<span class="token punctuation">,</span> position<span class="token operator">:</span> <span class="token punctuation">{</span> commit<span class="token operator">:</span> options<span class="token punctuation">.</span>position<span class="token punctuation">,</span> prepare<span class="token operator">:</span> options<span class="token punctuation">.</span>position <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">as</span> <span class="token builtin">unknown</span> <span class="token keyword">as</span> AllStreamRecordedEvent<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Only 130 lines, but a lot is happening. I’m building the test scenario using <a href="https://en.wikipedia.org/wiki/Specification_pattern">Specification pattern</a>.</p> <p>When a method takes a sequence of events, as I expect to handle idempotency checks and verify updates based on the event position reflected in the read model data, I assume it is passed through event metadata. If my subscription is to a specific stream, then I’ll use stream revision, and if I’m subscribing to all events, then I’ll use global position (read more in <a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>.</p> <p>The test specification is getting the projection logic, also called initially on the events we passed through the setup. Then for the event we want to verify. Of course, we could pass the specific read model state as test input. It would make setup a bit faster, but if we always use events, we’ll always test real scenarios generated by projection. As always, pick your poison.</p> <p>As MongoDB is eventually consistent, we might need to retry our reads multiple times until changes are applied; we’re using the <em>retryIfNotFound</em> for that.</p> <p>As you see, there is not a lot of code and not much fancy stuff. As long as we know what may happen and what we need to verify, it’s not a lot of work to ensure it.</p> <p><strong>If this description is not thorough enough, check the full webinar <a href="https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven">Design and test Event-Driven projections and read models</a> I made for Architecture Weekly subscribers.</strong></p> <p><a href="https://www.architecture-weekly.com/p/webinar-7-design-and-test-event-driven"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABYUlEQVQoz3WRXUvDMBSGT5J+rE3SpUvbpO3SdnN1Y+wDpzfC2A8Qb/SXCAqCt177GwR/qaQTdB/CexE4POd9DgGgCrGcq6XIZp4oIVQQJBCkEGZA1d8gpsGTxXj+8f769vw0Xd8CUIVZHg6acFCFctiLjRtXPVkirhFV6JCHIGNpvVptmot5KA10K4tYtWXTFnXL09oVFsZMoy5H/dbIlZja1Xs4F/l6PLvpqwmJjB13zihIcZCcyCvMNOGF3QtUOZF2+qOsWmozQ7z2RO1EJfjJ5+P918Md+MlpP2Y5onrfrAk3XC+4WoTJJebWGTz5stu+7rbgyXPwr7YmUeXHE6ffaXczYBrcgc3RzedgQ5M6GJRAU6DZ/mzCNeH5WfjHxb6oxtzIqk2bmSfbIJl6YkQiY686Rx7AQLUTGV+OiBhiPsS8dCJD+PA/8rCZacyMKK65vorylSg3fjyCk086hb8BzyQr/GEuUwkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/5aab10549832b4ee324ada5016d0bcf4/a331c/2023-04-29-webinar.png" srcset="/static/5aab10549832b4ee324ada5016d0bcf4/36ca5/2023-04-29-webinar.png 200w, /static/5aab10549832b4ee324ada5016d0bcf4/a3397/2023-04-29-webinar.png 400w, /static/5aab10549832b4ee324ada5016d0bcf4/a331c/2023-04-29-webinar.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p>Check the complete code in <a href="https://github.com/oskardudycz/EventSourcing.NodeJS/tree/main/samples/decider/src/shoppingCarts/shoppingCartDetails">my sample repository</a>.</p> <p>Read also:</p> <ul> <li><a href="/en/testing_event_sourcing/">Testing business logic in Event Sourcing, and beyond!</a></li> <li><a href="/en/behaviour_driven_design_is_not_about_tests/">Behaviour-Driven Design is more than tests</a></li> <li><a href="/en/ogooreck_sneaky_bdd_testing_framework/">Ogooreck, a sneaky testing library in BDD style</a></li> <li><a href="/en/writing_and_testing_business_logic_in_fsharp/">Writing and testing business logic in F#</a></li> <li><a href="/en/i_tested_on_production/">I tested it on production and I’m not ashamed of it</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Publishing read model changes from Marten]]>https://event-driven.io/en/publishing_read_model_changes_from_marten/https://event-driven.io/en/publishing_read_model_changes_from_marten/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/22ae2719cb7e11f5cc93cdac3931369c/a331c/2023-04-23-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7AAAAOwAFq1okJAAABMElEQVQoz6VSS0/CQBDeq9DuzE67224p0PIosJIWKqIJGEQlMUYTSZToTY/+/z9gaLngAYx+h8m8vi+ZBxP/ADtcph0E0TFy0SYQtz4iAqBlQ8XiFYtbNi869lR2ZBKCA1YszgGVlIAYeF6o/bNBvMi7i7yb9SNEtDlYNgiBTqGxJaPYZpNmMB3E76v0eTmq12qX2eDtJv+8n6ym5mWZvt5NhCDTboz7kSA6qdqIyBBRue6w3fhYjdcz8zAbJq0w0H4nqme9yHWcKgdBJKU0Sftpnn89nm+u09moq33JwkDPx8Or3FykPaWkIIeIOAdPKSgmL7dARNpTSkoimpp4c5udJjGbpKbfiXytaH+hJe1HBgvLAULttZo15rrEARDw9+el3S2AlcEfQEef5PCHfQMgVjr8pKcgLQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/22ae2719cb7e11f5cc93cdac3931369c/a331c/2023-04-23-cover.png" srcset="/static/22ae2719cb7e11f5cc93cdac3931369c/36ca5/2023-04-23-cover.png 200w, /static/22ae2719cb7e11f5cc93cdac3931369c/a3397/2023-04-23-cover.png 400w, /static/22ae2719cb7e11f5cc93cdac3931369c/a331c/2023-04-23-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Integrations have different names, shades and colours, but only one adjective: <em>challenging</em>.</strong> Trying to glue systems together requires matching two visions into one. That’s never easy, as different tools have different purposes, and authors cannot predict all the permutations that users can come up with. But no one said that all has to be easy, right? And no one said that we could not try to make it easy.</p> <p><strong>We’re trying to achieve that in <a href="https://martendb.io/">Marten</a>, making the event-driven world accessible.</strong> I wrote already about that in the past:</p> <ul> <li><a href="/en/integrating_Marten/">Integrating Marten with other systems</a></li> <li><a href="/en/projecting_from_marten_to_elasticsearch/">Projecting Marten events to Elasticsearch</a>.</li> </ul> <p><strong>Today, I want to tell you the easiest way to forward changes to Marten read models into other services.</strong></p> <p><a href="/en/projections_in_marten_explained/">Marten has a built-in way to build read models from stored events</a>. There are various ways, but building a read model out of events stored in a single stream is simplest.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProjectCreated</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProjectId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProjectStarted</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProjectId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> StartedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ManagerAssignedToProject</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProjectId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ManagerId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProjectInfo</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> StartedAt <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> ManagerId <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProjectInfoProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">SingleStreamProjection<span class="token punctuation">&lt;</span>ProjectInfo<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProjectInfo</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">ProjectCreated</span> created<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>created<span class="token punctuation">.</span>ProjectId<span class="token punctuation">,</span> created<span class="token punctuation">.</span>Name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProjectInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProjectStarted</span> started<span class="token punctuation">,</span> <span class="token class-name">ProjectInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> StartedAt <span class="token operator">=</span> started<span class="token punctuation">.</span>StartedAt <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProjectInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ManagerAssignedToProject</span> managerAssigned<span class="token punctuation">,</span> <span class="token class-name">ProjectInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> ManagerId <span class="token operator">=</span> managerAssigned<span class="token punctuation">.</span>ManagerId <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Marten can update projections in the same transaction as we’re appending events and in an async way. We selected the asynchronous way, as we don’t want to slow our writing. Then we can register it via:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProjectInfoProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Now, let’s say that we’re living in the <a href="/en/how_to_cut_microservices/">Microservices world</a>, and we’d like to build local read models based on our project info in other modules.</strong> We are motivated to have the local copy to make our read models and lookups resilient, not needing to query the projects module. There are many ways to achieve that, but the most popular is <a href="https://martinfowler.com/articles/201701-event-driven.html">Event-Carried State Transfer</a>. I’m not a huge fan of this approach because we should focus more on behaviour rather than the state in an event-driven way to not fall into <a href="/en/state-obsession/">state obsession</a>. Still, for our motivation, it can be an acceptable choice.</p> <p>How to do it?</p> <p>Marten allows <a href="https://martendb.io/diagnostics.html#listening-for-document-store-events">listening to changes</a>. We can use it both for document changes, events and inline and async changes. We can use <em>IDocumentSessionListener</em>, <em>IChangeListener</em> or their abstract implementation <em>DocumentSessionListenerBase</em>. How can it help in our case?</p> <p><strong>Document session listener has <em>AfterCommitAsync</em> method that’s triggered after all changes in the asynchronous processing were made, but BEFORE the transaction was committed.</strong> I’ll explain why it’s important later on. Now, let’s say that we’re using some messaging system (Kafka, RabbitMQ, etc.); replace this dummy interface with your favourite one:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMessagingSystem</span> <span class="token punctuation">{</span> <span class="token return-type class-name">Task</span> <span class="token function">Publish</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> messages<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Having it, we can define the following document listener to forward the changes to your messaging system. I’ll use <em>IChangeListener</em> as I’m only interested in the asynchronous processing.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AsyncDocumentChangesForwarder</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IChangeListener</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IMessagingSystem</span> messagingSystem<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">AsyncDocumentChangesForwarder</span><span class="token punctuation">(</span><span class="token class-name">IMessagingSystem</span> messagingSystem<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>messagingSystem <span class="token operator">=</span> messagingSystem<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">AfterCommitAsync</span><span class="token punctuation">(</span><span class="token class-name">IDocumentSession</span> session<span class="token punctuation">,</span> <span class="token class-name">IChangeSet</span> commit<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> token<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> changes <span class="token operator">=</span> commit<span class="token punctuation">.</span>Inserted<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>doc <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DocumentChanged</span><span class="token punctuation">(</span>ChangeType<span class="token punctuation">.</span>Insert<span class="token punctuation">,</span> doc<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>commit<span class="token punctuation">.</span>Updated<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>doc <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DocumentChanged</span><span class="token punctuation">(</span>ChangeType<span class="token punctuation">.</span>Update<span class="token punctuation">,</span> doc<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>commit<span class="token punctuation">.</span>Deleted<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>doc <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DocumentChanged</span><span class="token punctuation">(</span>ChangeType<span class="token punctuation">.</span>Delete<span class="token punctuation">,</span> doc<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> messagingSystem<span class="token punctuation">.</span><span class="token function">Publish</span><span class="token punctuation">(</span>changes<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Cast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">DocumentChanged</span><span class="token punctuation">(</span><span class="token class-name">ChangeType</span> ChangeType<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> Data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">ChangeType</span> <span class="token punctuation">{</span> Insert<span class="token punctuation">,</span> Update<span class="token punctuation">,</span> Delete <span class="token punctuation">}</span> </code></pre></div> <p>It takes the dependency on your service messaging system and translates Marten’s change information into the unified event with information about the change. Of course, it’s a simplified version, you should align it with your requirements and the tool you use, but the pattern will stay the same.</p> <p>We can register it by extending our Marten configuration.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProjectInfoProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// register listener</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span>AsyncListeners<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">AsyncDocumentChangesForwarder</span><span class="token punctuation">(</span>messagingSystemStub<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// define retry policy</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">OnException</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Exception<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">RetryLater</span><span class="token punctuation">(</span><span class="token number">50</span><span class="token punctuation">.</span><span class="token function">Milliseconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">250</span><span class="token punctuation">.</span><span class="token function">Milliseconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">500</span><span class="token punctuation">.</span><span class="token function">Milliseconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>Then<span class="token punctuation">.</span><span class="token function">Pause</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">.</span><span class="token function">Seconds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We can register multiple async listeners.</p> <p>Besides the listener, I also defined a custom exception policy to show that Marten supports more advanced error handling, failover scenarios like dead letter queue etc. (read more in <a href="https://martendb.io/events/projections/async-daemon.html#error-handling">docs</a>).</p> <p><strong>That’s essential for our scenario. If storing documents fails, but changes forwarding to messaging system will fail, then none of the changes will be stored, and processing will be retried.</strong></p> <p>Thanks to that, we have a proper implementation of <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">outbox pattern</a> and at least one delivery guarantee. We also have decoupled processing between updating read models and publishing messages.</p> <p>And that’s pretty cool, isn’t it?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Postgres Superpowers in Practice]]>https://event-driven.io/en/postgres_superpowers/https://event-driven.io/en/postgres_superpowers/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/45432bd59909acaa65d9150df4e3b0bb/a331c/2023-04-15-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7BAAAOwQG4kWvtAAACbUlEQVQoz03Q6UsTcADG8Z95lC8yBmkvgiCxhELohUFUFBGR0EFlSfmiG5PUCokwLClK0uzwoMw8Ku1gpi6F1EzbUPJqZW6aTm0zdejydmrObb9vzN70B3y+Dzzi23PalBhKMVZiqedPN4xiNjsqO+ZSvzoyaq2V6t9V1dP9Gj7nUXiNZxfIjiTzNI9OIHqrGWpg/DuznTgGwIYTdpWYRHqH14P25bc14lyRIsnQ+cM+rqXpJTWPqLiH6iZv4hCTOmw9SLNrEBuAXXJRa/F7PbinsE2RZRIJrcuSOowjNkYZqKejnNZimvLRPEHYe2EYrGBHSgmYf09diSnwSdFvf9qw6sitFbuTi1uHADkv50yMtLgS3VXoVAgGYRLmcTmYmCeowBioCPMNL/WKKz/ps23n4pD0902AU0o5hu0nE+0MfcH06T/sXNCGMZtnap9HpCog4Kr34Yc+oRne686sOp87s5CWVpx9TBsYbqH/M2LOiLTANDj+jcuE5sn98cp478DiiDUHA/09fDfeP3YJ84BzZlYO2+eNTLZj0WJSIyxarJ3Y+2EEaXPx6SlrVFDMPreIkBXhB5bGBovNKYvEO3ePur1hji45qsPcIH/W0FaG0JfSU8NgI+M6Zn+5cF7ziPuh/A3+R7coVq733BbsF3rJTaR5LdGmFY21YSh36FV8eUVtNqLiPposGl/SUuL6YMbIjrQecVwd/UxDV8umY4kiXLlVsbo3/e2EAe0rZ20WVamUJVIYj8iJpuAyymuobqN+THORPSqna23sx+t3PvQ0jJ292xiSXK98oe9TU5crS25QEEteNFkRZJ7iL8gq5CcQMXGTAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/45432bd59909acaa65d9150df4e3b0bb/a331c/2023-04-15-cover.png" srcset="/static/45432bd59909acaa65d9150df4e3b0bb/36ca5/2023-04-15-cover.png 200w, /static/45432bd59909acaa65d9150df4e3b0bb/a3397/2023-04-15-cover.png 400w, /static/45432bd59909acaa65d9150df4e3b0bb/a331c/2023-04-15-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="https://www.youtube.com/watch?v=I_PupTXEcXU">Look! Up in the sky! It’s a bird! It’s a plane! It’s Superman!</a></strong> I have such a thought quite often while working with Postgres. Why?</p> <p><strong>Let’s say that you’re building Car Fleet Management System.</strong> You must manage all data about the company’s cars, drivers, trips, fuel management, etc. In a nutshell, that’s more around accounting and compliance than driving. If your company is a big one in the logistics space, you have a lot of data to manage and process.</p> <p>Let’s start with a simple case: trip management. We could come up with the following table:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> trips <span class="token punctuation">(</span> trip_time TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> vehicle_id <span class="token keyword">INT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> driver_name <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> start_location <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> end_location <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> distance_kilometers <span class="token keyword">NUMERIC</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> fuel_used_liters <span class="token keyword">NUMERIC</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span>trip_time<span class="token punctuation">,</span> vehicle_id<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It’s a simplified version, but it covers basic needs. We have trip per vehicle, driver information, how long the trip was, and how much fuel was used. That doesn’t look scary, but if we’d like to make it fast and scalable for reporting and alerting needs, then we should do better than that. Of course, we could add some indexes, but we might still need more adjustments. The data size could grow, and querying and processing might need to be faster. Especially keeping in mind that this may be just the centrepiece of the normalised table schema, add into that table that tracks online the GPS location, and the size of data is skyrocketing like Superman in the sky.</p> <p><strong><a href="https://www.postgresql.org/docs/current/ddl-partitioning.html">Postgres provides built-in partitioning capabilities</a>.</strong> In a nutshell, we can define what data from the table we’re using to partition our data. Data will be physically stored in different disk locations grouped by partition criteria. Postgres will handle the routing of inserts, queries, etc. We can still use the table as the regular one.</p> <p>In our case, we could use the partitioning-by-date strategy because we’ll be primarily interested in trips in a selected time range. We can do that by adding <em>PARTITION BY RANGE (trip_time)</em> in our table definition:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> trips <span class="token punctuation">(</span> trip_time TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> vehicle_id <span class="token keyword">INT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> driver_name <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> start_location <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> end_location <span class="token keyword">TEXT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> distance_kilometers <span class="token keyword">NUMERIC</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> fuel_used_liters <span class="token keyword">NUMERIC</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span>trip_time<span class="token punctuation">,</span> vehicle_id<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token keyword">PARTITION</span> <span class="token keyword">BY</span> RANGE <span class="token punctuation">(</span>trip_time<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We don’t need to change our queries or inserts. We can also detach and attach those partitions by a single command, which makes ops work much easier.</p> <p>That’s neat, and the topic is on the blog by itself. Still, it has some tedious parts. For instance, you need to define partitions explicitly upfront; Postgres won’t create them automatically while inserting data.</p> <p>Typically, there’s some CRON job setting up partitions, e.g.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">DO</span> $$<span class="token keyword">DECLARE</span> month_start_date <span class="token keyword">DATE</span> :<span class="token operator">=</span> <span class="token string">'2023-01-01'</span><span class="token punctuation">;</span> month_end_date <span class="token keyword">DATE</span> :<span class="token operator">=</span> <span class="token string">'2023-12-01'</span><span class="token punctuation">;</span> <span class="token keyword">BEGIN</span> <span class="token keyword">WHILE</span> month_start_date <span class="token operator">&lt;</span> month_end_date <span class="token keyword">LOOP</span> <span class="token keyword">EXECUTE</span> <span class="token function">format</span><span class="token punctuation">(</span><span class="token string">' CREATE TABLE trips_%s PARTITION OF trips FOR VALUES FROM (%L) TO (%L);'</span><span class="token punctuation">,</span> TO_CHAR<span class="token punctuation">(</span>month_start_date<span class="token punctuation">,</span> <span class="token string">'YYYY_MM'</span><span class="token punctuation">)</span><span class="token punctuation">,</span> month_start_date<span class="token punctuation">,</span> month_start_date <span class="token operator">+</span> <span class="token keyword">INTERVAL</span> <span class="token string">'1 month'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> month_start_date :<span class="token operator">=</span> month_start_date <span class="token operator">+</span> <span class="token keyword">INTERVAL</span> <span class="token string">'1 month'</span><span class="token punctuation">;</span> <span class="token keyword">END</span> <span class="token keyword">LOOP</span><span class="token punctuation">;</span> <span class="token keyword">END</span>$$<span class="token punctuation">;</span></code></pre></div> <p>Not that terrible, but with a bigger scale, managing that can get complicated. When you cannot predict partitions upfront and set up dynamic ones upon insert, you’d need to use triggers or <a href="https://github.com/pgpartman/pg_partman">pg_partman</a> extension. But as we’re in the time series use case, then…</p> <h2 id="introducing-timescaledb" style="position:relative;"><a href="#introducing-timescaledb" aria-label="introducing timescaledb permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Introducing TimescaleDB</h2> <p>Postgres is not only Superman but also Transformer. From the ground basis, it is built to support extensions without changing the core database system. PostgreSQL extensions can enhance the core features with new data types, functions, operators, indexing methods, and more. You can write them in languages like C, Rust, etc. Extensions are a way to package and distribute these additional features, making it easy for users to install and manage them.</p> <p>There are a lot of mature plugins provided by external companies and communities. Most of them are open-sourced and free. One of them is <a href="https://www.timescale.com/">TimescaleDB</a>. Their slogan is <em>“Postgres for time-series”</em>. And it’s like that. It provides various tooling to make your time-based analytics and data processing easier and faster. You can <a href="https://github.com/oskardudycz/postgres-for-dotnet-dev/blob/888f2c817cc9a871daffadaefdaf9e49cc132c42/Dockerfile#L5">install it on your Postgres installation for free</a>, but most hostings (also big cloud providers) provide you with an option to enable it out of the box.</p> <p>Once you have it, toggle it with the following SQL:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> EXTENSION <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> timescaledb<span class="token punctuation">;</span></code></pre></div> <p>It’s a Postgres built-in syntax and a general pattern for enabling extensions.</p> <p>Ok, getting back to TimescaleDB and partitioning. Postgres has a feature that maybe is not as super as Superman, but at least it’s hyper.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> create_hypertable<span class="token punctuation">(</span><span class="token string">'trips'</span><span class="token punctuation">,</span> <span class="token string">'trip_time'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><em>create_hypertable</em> is a custom function that enables partitioning on TimescaleDB steroids. We don’t need to create partitions; TimescaleDB will handle that for us and do other performance optimisation.</p> <p><strong>That’s also why TimescaleDB is an excellent solution for IoT.</strong> When we have a huge data coming in a short period, the built-in internal capabilities can optimise the ingress for us.</p> <p>That’s sweet, but let’s not stop here and just do simple queries you can imagine (like average distance in a date range, etc.).</p> <p><strong>Let’s build a report calculating the average fuel efficiency in the last 30 days.</strong> This is quite useful in Fleet Management to detect fraud on people forgetting to log all trips, using the car for their needs, etc. We can do that by getting the average usage per kilometre. It’s, of course, a simplified scenario, but you get the idea.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> MATERIALIZED <span class="token keyword">VIEW</span> vehicle_fuel_efficiency_avg <span class="token keyword">WITH</span> <span class="token punctuation">(</span>timescaledb<span class="token punctuation">.</span>continuous<span class="token punctuation">)</span> <span class="token keyword">AS</span> <span class="token keyword">SELECT</span> time_bucket<span class="token punctuation">(</span><span class="token string">'1 day'</span><span class="token punctuation">,</span> trip_time<span class="token punctuation">)</span> <span class="token keyword">AS</span> bucket<span class="token punctuation">,</span> vehicle_id<span class="token punctuation">,</span> <span class="token function">AVG</span><span class="token punctuation">(</span>distance_kilometers<span class="token punctuation">)</span><span class="token operator">/</span><span class="token function">AVG</span><span class="token punctuation">(</span>fuel_used_liters<span class="token punctuation">)</span> <span class="token keyword">AS</span> fuel_efficiency_avg <span class="token keyword">FROM</span> trips <span class="token keyword">WHERE</span> trip_time <span class="token operator">>=</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token keyword">INTERVAL</span> <span class="token string">'30 days'</span> <span class="token keyword">GROUP</span> <span class="token keyword">BY</span> bucket<span class="token punctuation">,</span> vehicle_id<span class="token punctuation">;</span></code></pre></div> <p><strong>Materialised views are a feature that enables you to build a read-only aggregation of your table data; what’s more, they’re not calculated on the fly while doing queries but stored on disk, thus getting better performance.</strong> They’re automatically available in Postgres and cool enough, but again a bit tedious, as you need to trigger their recalculation manually. That’s necessary, as rebuilding them may take time and be resource-demanding. You probably know where I’m going; yes, TimescaleDB can help you with the <a href="https://docs.timescale.com/getting-started/latest/create-cagg/">continuous aggregates</a> feature.</p> <p>We need to mark our materialised view using <em>WITH (timescaledb.continuous)</em> and add policy:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> add_continuous_aggregate_policy<span class="token punctuation">(</span> continuous_aggregate <span class="token operator">=</span><span class="token operator">></span> <span class="token string">'vehicle_fuel_efficiency_avg'</span><span class="token punctuation">,</span> start_offset <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">INTERVAL</span> <span class="token string">'30 days'</span><span class="token punctuation">,</span> end_offset <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">INTERVAL</span> <span class="token string">'1 second'</span><span class="token punctuation">,</span> schedule_interval <span class="token operator">=</span><span class="token operator">></span> <span class="token keyword">INTERVAL</span> <span class="token string">'1 day'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re defining the initial date range of recalculation (from 30 days till 1 second ago) and the interval in which it should be updated (1 day). From now on, TimescaleDB will refresh materialised view for us automatically.</p> <p><strong>TimescaleDB extends Postgres also with a cron-like scheduler.</strong> It uses it internally to update materialised views. We’ll use it later.</p> <p>By the way, <em>Interval</em> is also a decent example of <a href="https://www.postgresql.org/docs/current/sql-createtype.html">custom types</a> feature Postgres provides. TimescaleDB defines this one, but you can define your own.</p> <h2 id="generating-alerts" style="position:relative;"><a href="#generating-alerts" aria-label="generating alerts permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Generating alerts</h2> <p>Let’s use our materialised view to generate alerts based on detected fuel usage anomalies. This table could look as:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> fuel_efficiency_alerts <span class="token punctuation">(</span> vehicle_id <span class="token keyword">INT</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> start_time TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> end_time TIMESTAMPTZ <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> fuel_efficiency <span class="token keyword">NUMERIC</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span>vehicle_id<span class="token punctuation">,</span> start_time<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Let’s not define it as <em>hypertable</em>; why? I’ll explain that later, for now, trust me, that’s better. We won’t be querying it; just getting information about the new records.</p> <p>How to generate alerts? Let’s define a function for that:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token operator">OR</span> <span class="token keyword">REPLACE</span> <span class="token keyword">FUNCTION</span> check_fuel_efficiency_and_insert_alerts<span class="token punctuation">(</span>p_job_id <span class="token keyword">INTEGER</span><span class="token punctuation">,</span> p_config JSONB<span class="token punctuation">)</span> <span class="token keyword">RETURNS</span> VOID <span class="token keyword">AS</span> $$ <span class="token keyword">BEGIN</span> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> fuel_efficiency_alerts <span class="token punctuation">(</span> vehicle_id<span class="token punctuation">,</span> start_time<span class="token punctuation">,</span> end_time<span class="token punctuation">,</span> fuel_efficiency <span class="token punctuation">)</span> <span class="token keyword">SELECT</span> vehicle_id<span class="token punctuation">,</span> bucket <span class="token keyword">AS</span> start_time<span class="token punctuation">,</span> bucket <span class="token operator">+</span> <span class="token keyword">INTERVAL</span> <span class="token string">'1 day'</span> <span class="token keyword">AS</span> end_time<span class="token punctuation">,</span> fuel_efficiency_avg <span class="token keyword">AS</span> fuel_efficiency <span class="token keyword">FROM</span> vehicle_fuel_efficiency_avg <span class="token keyword">WHERE</span> fuel_efficiency_avg <span class="token operator">&lt;</span> <span class="token number">5</span> <span class="token operator">AND</span> bucket <span class="token operator">>=</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token keyword">INTERVAL</span> <span class="token string">'30 days'</span> <span class="token keyword">ON</span> CONFLICT <span class="token punctuation">(</span>vehicle_id<span class="token punctuation">,</span> start_time<span class="token punctuation">)</span> <span class="token keyword">DO</span> <span class="token keyword">UPDATE</span> <span class="token keyword">SET</span> fuel_efficiency <span class="token operator">=</span> EXCLUDED<span class="token punctuation">.</span>fuel_efficiency<span class="token punctuation">,</span> end_time <span class="token operator">=</span> EXCLUDED<span class="token punctuation">.</span>end_time<span class="token punctuation">;</span> <span class="token keyword">DELETE</span> <span class="token keyword">FROM</span> fuel_efficiency_alerts <span class="token keyword">AS</span> a <span class="token keyword">WHERE</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> <span class="token punctuation">(</span> <span class="token keyword">SELECT</span> <span class="token number">1</span> <span class="token keyword">FROM</span> vehicle_fuel_efficiency_avg <span class="token keyword">AS</span> f <span class="token keyword">WHERE</span> a<span class="token punctuation">.</span>vehicle_id <span class="token operator">=</span> f<span class="token punctuation">.</span>vehicle_id <span class="token operator">AND</span> f<span class="token punctuation">.</span>bucket <span class="token operator">>=</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token keyword">INTERVAL</span> <span class="token string">'30 days'</span> <span class="token operator">AND</span> a<span class="token punctuation">.</span>start_time <span class="token operator">=</span> f<span class="token punctuation">.</span>bucket <span class="token operator">AND</span> f<span class="token punctuation">.</span>fuel_efficiency_avg <span class="token operator">&lt;</span> <span class="token number">5</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">END</span><span class="token punctuation">;</span> $$ <span class="token keyword">LANGUAGE</span> plpgsql<span class="token punctuation">;</span></code></pre></div> <p>Nothing fancy here; we’re generating alerts based on the <em>magic factor</em> of average fuel usage (equal to 5), inserting or updating the current alert information and cleaning obsolete alerts.</p> <p><strong>I told you before that I’ll use the TimescaleDB scheduler.</strong> Now it’s the right time.</p> <p>We can do it by calling the following function:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> add_job<span class="token punctuation">(</span><span class="token string">'check_fuel_efficiency_and_insert_alerts'</span><span class="token punctuation">,</span> <span class="token string">'5 seconds'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It tells which function should be called in which interval. Simple as that!</p> <p>Having the data, we can generate a report that shows the fuel efficiency for each vehicle over time, as well as any alerts that have been generated.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> trips<span class="token punctuation">.</span>vehicle_id<span class="token punctuation">,</span> trips<span class="token punctuation">.</span>trip_time<span class="token punctuation">,</span> trips<span class="token punctuation">.</span>distance_kilometers<span class="token operator">/</span>trips<span class="token punctuation">.</span>fuel_used_liters <span class="token keyword">AS</span> fuel_efficiency<span class="token punctuation">,</span> fuel_efficiency_alerts<span class="token punctuation">.</span>start_time<span class="token punctuation">,</span> fuel_efficiency_alerts<span class="token punctuation">.</span>end_time <span class="token keyword">FROM</span> trips <span class="token keyword">LEFT</span> <span class="token keyword">JOIN</span> vehicle_fuel_efficiency_avg <span class="token keyword">ON</span> trips<span class="token punctuation">.</span>vehicle_id <span class="token operator">=</span> vehicle_fuel_efficiency_avg<span class="token punctuation">.</span>vehicle_id <span class="token operator">AND</span> time_bucket<span class="token punctuation">(</span><span class="token string">'1 day'</span><span class="token punctuation">,</span> trips<span class="token punctuation">.</span>trip_time<span class="token punctuation">)</span> <span class="token operator">=</span> vehicle_fuel_efficiency_avg<span class="token punctuation">.</span>bucket <span class="token keyword">LEFT</span> <span class="token keyword">JOIN</span> fuel_efficiency_alerts <span class="token keyword">ON</span> trips<span class="token punctuation">.</span>vehicle_id <span class="token operator">=</span> fuel_efficiency_alerts<span class="token punctuation">.</span>vehicle_id <span class="token keyword">WHERE</span> trips<span class="token punctuation">.</span>trip_time <span class="token operator">>=</span> <span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token keyword">INTERVAL</span> <span class="token string">'30 days'</span> <span class="token keyword">ORDER</span> <span class="token keyword">BY</span> trips<span class="token punctuation">.</span>vehicle_id<span class="token punctuation">,</span> trips<span class="token punctuation">.</span>trip_time<span class="token punctuation">;</span></code></pre></div> <p>If that’s not fancy enough, let’s make it even fancier!</p> <h2 id="adding-postgis" style="position:relative;"><a href="#adding-postgis" aria-label="adding postgis permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Adding PostGIS</h2> <p>To not make this a TimescaleDB love poem, let’s introduce another extension to give you broader coverage of the Postgres capabilities.</p> <p><a href="https://postgis.net/">PostGIS</a> is a plugin that enables advanced storage and transformation of spatial data, so geographic locations etc. It’s compatible with most of the standards for storing and transforming positions. Sounds like we could use for our trips, aye?</p> <p>Let’s enable PostGIS:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> EXTENSION <span class="token keyword">IF</span> <span class="token operator">NOT</span> <span class="token keyword">EXISTS</span> postgis<span class="token punctuation">;</span></code></pre></div> <p>And extend our table with route information.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span> trips <span class="token keyword">ADD</span> <span class="token keyword">COLUMN</span> route <span class="token keyword">GEOMETRY</span><span class="token punctuation">(</span><span class="token keyword">LINESTRING</span><span class="token punctuation">,</span> <span class="token number">4326</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span><span class="token punctuation">;</span></code></pre></div> <p><em>GEOMETRY</em> is one of the types that PostGIS is adding. It allows you to store multiple points inside a single column. Of course, in the real world, we’d keep the GPS recordings in a dedicated table, but we could also keep a summary with the main route points in the trips table. We could use it to display it on the map in the UI.</p> <p>If we store our start and end position in a format <em>{Latitude}, {Longitude}</em>, e.g. <em>52.292064, 21.036320</em>, we can also provide a default value based on the start and end locations.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">UPDATE</span> trips <span class="token keyword">SET</span> route <span class="token operator">=</span> ST_Transform<span class="token punctuation">(</span> ST_MakeLine<span class="token punctuation">(</span> ST_GeomFromText<span class="token punctuation">(</span> <span class="token string">'POINT('</span> <span class="token operator">||</span> split_part<span class="token punctuation">(</span>start_location<span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">' '</span> <span class="token operator">||</span> split_part<span class="token punctuation">(</span>start_location<span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">')'</span><span class="token punctuation">,</span> <span class="token number">4326</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> ST_GeomFromText<span class="token punctuation">(</span> <span class="token string">'POINT('</span> <span class="token operator">||</span> split_part<span class="token punctuation">(</span>end_location<span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">' '</span> <span class="token operator">||</span> split_part<span class="token punctuation">(</span>end_location<span class="token punctuation">,</span> <span class="token string">','</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token string">')'</span><span class="token punctuation">,</span> <span class="token number">4326</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">4326</span><span class="token punctuation">)</span> <span class="token keyword">WHERE</span> route <span class="token operator">IS</span> <span class="token boolean">NULL</span><span class="token punctuation">;</span> <span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span> trips <span class="token keyword">ALTER</span> <span class="token keyword">COLUMN</span> route <span class="token keyword">SET</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">;</span></code></pre></div> <p><strong>You already see examples of the multiple functionalities that PostGis provides, e.g. parsing points and creating lines from them.</strong></p> <p>And hey, why not go further? We could notice that distance kilometres and start and end locations could be derived from route information. Why do we always need to calculate them when inserting? Actually, there’s no need, as vanilla Postgres can help with that!</p> <h2 id="generated-columns" style="position:relative;"><a href="#generated-columns" aria-label="generated columns permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Generated columns</h2> <p><strong>Postgres provides an option to automatically compute the column’s value based on the data from others and store it for you inside the other column. This feature is called <a href="https://www.postgresql.org/docs/current/ddl-generated-columns.html">Generated columns</a>.</strong> Let’s showcase them by calculating data derived from the route in our trip table.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span> trips <span class="token keyword">DROP</span> <span class="token keyword">COLUMN</span> distance_kilometers<span class="token punctuation">,</span> <span class="token keyword">ADD</span> <span class="token keyword">COLUMN</span> distance_kilometers <span class="token keyword">NUMERIC</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">,</span> <span class="token number">2</span><span class="token punctuation">)</span> GENERATED ALWAYS <span class="token keyword">AS</span> <span class="token punctuation">(</span> ST_Length<span class="token punctuation">(</span>route::geography<span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">1000</span> <span class="token punctuation">)</span> STORED<span class="token punctuation">;</span> <span class="token keyword">DROP</span> <span class="token keyword">COLUMN</span> start_location<span class="token punctuation">,</span> <span class="token keyword">ADD</span> <span class="token keyword">COLUMN</span> start_location <span class="token keyword">GEOMETRY</span><span class="token punctuation">(</span><span class="token keyword">POINT</span><span class="token punctuation">,</span> <span class="token number">4326</span><span class="token punctuation">)</span> GENERATED ALWAYS <span class="token keyword">AS</span> <span class="token punctuation">(</span> ST_StartPoint<span class="token punctuation">(</span>route<span class="token punctuation">)</span> <span class="token punctuation">)</span> STORED<span class="token punctuation">;</span> <span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span> trips <span class="token keyword">DROP</span> <span class="token keyword">COLUMN</span> end_location<span class="token punctuation">,</span> <span class="token keyword">ADD</span> <span class="token keyword">COLUMN</span> end_location <span class="token keyword">GEOMETRY</span><span class="token punctuation">(</span><span class="token keyword">POINT</span><span class="token punctuation">,</span> <span class="token number">4326</span><span class="token punctuation">)</span> GENERATED ALWAYS <span class="token keyword">AS</span> <span class="token punctuation">(</span> ST_EndPoint<span class="token punctuation">(</span>route<span class="token punctuation">)</span> <span class="token punctuation">)</span> STORED<span class="token punctuation">;</span></code></pre></div> <p>As you see, we can also use custom functions from plugins in it!</p> <h2 id="logical-replication" style="position:relative;"><a href="#logical-replication" aria-label="logical replication permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Logical Replication</h2> <p>If you read everything in the previous paragraphs, you deserve the Grand Finale!</p> <p>I explained the superpowers of Postgres logical replication in detail in <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">Push-based Outbox Pattern with Postgres Logical Replication</a>.</p> <blockquote> <p>Postgres have a concept called “Write-Ahead Log” (WAL). It is an append-only structure that records all the operations during transaction processing (Inserts, Updates, Deletes). When we commit a transaction, the data is firstly appended to the Write-Ahead Log. Then all operations are applied to tables, indexes, etc. Hence the name “Write-Ahead”: from this writing data to the log in advance of other changes. So from that perspective, tables and indexes are just read models for Write-Ahead Log.</p> <p>Postgres is a rock-solid database with many superb features. One of them is JSON support we’re using in Marten, and the other is logical replication that we’ll look closer at now.</p> <p>Logical replication takes the traditional approach to the next level. Instead of sending the raw binary stream of backed-up database files, we’re sending a stream of changes that were recorded in the Write-Ahead Log. It’s named logical, as it understands the operations’ semantics, plus the information about the tables it’s replicating. It’s highly flexible; it can be defined for one or multiple tables, filter records and copy a subset of data. It can inform you about changes to specific records. Thus it requires the replicated table to have primary keys.</p> </blockquote> <p>We’ll use it to publish our notifications from the alerts table into the web UI! I’ll use C#, .NET and <a href="https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-8.0">SignalR</a>, but you can apply this pattern to other technologies.</p> <p>We’ll subscribe to the changes from the alerts table (<em>vehicle_fuel_efficiency_avg</em>). That’s also why we didn’t make it a <em>hypertable</em>. Reminder: it uses partitioning underneath. Postgres, behind the scenes, is creating the table for each partition. Technically, it’s possible to tell Postgres to replicate data from those tables, but if we add to that dynamic creation etc., things are getting harder. That’s also why TimescaleDB discourages using logical replication for <em>hypertables</em>. That may change, as Postgres is investing a lot of effort to make logical replication and partitioning more aligned and easier to use together. However, for now, let’s focus on our scenario. For our alerting case, it makes perfect sense, as we’re interested in new records, and they’re mostly ephemeral notifications.</p> <p>In C#, using the example code from <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">mentioned article</a> we can do it as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">FuelEfficiencyAlert</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">int</span></span> VehicleId<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> StartTime<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> EndTime<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> FuelEfficiency <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FuelEfficiencyAlertsPostgresSubscription</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">SubscribeAsync</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> connectionString<span class="token punctuation">,</span> <span class="token class-name">IHubContext<span class="token punctuation">&lt;</span>FleetManagementHub<span class="token punctuation">></span></span> hubContext<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> slotName <span class="token operator">=</span> <span class="token string">"fuel_efficiency_alerts_slot"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> dataMapper <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">FlatObjectMapper<span class="token punctuation">&lt;</span>FuelEfficiencyAlert<span class="token punctuation">></span></span><span class="token punctuation">(</span>NameTransformations<span class="token punctuation">.</span>FromPostgres<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> subscriptionOptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SubscriptionOptions</span><span class="token punctuation">(</span> connectionString<span class="token punctuation">,</span> slotName<span class="token punctuation">,</span> <span class="token string">"fuel_efficiency_alerts_pub"</span><span class="token punctuation">,</span> <span class="token string">"fuel_efficiency_alerts"</span><span class="token punctuation">,</span> dataMapper<span class="token punctuation">,</span> CreateStyle<span class="token punctuation">.</span>WhenNotExists <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> subscription <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Subscription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> alert <span class="token keyword">in</span> subscription<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>subscriptionOptions<span class="token punctuation">,</span> <span class="token named-parameter punctuation">ct</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> FleetManagementHub<span class="token punctuation">.</span><span class="token function">SendFuelEfficiencyAlert</span><span class="token punctuation">(</span>hubContext<span class="token punctuation">,</span> <span class="token punctuation">(</span>FuelEfficiencyAlert<span class="token punctuation">)</span> alert<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Behind the scenes, it’ll set up the publication for our table, replication slot, and subscribe for the changes.</p> <p>Each time a new record appears, we’ll get a notification from <a href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2019/november/csharp-iterating-with-async-enumerables-in-csharp-8">AsyncEnumerable</a> and forward it to <a href="https://learn.microsoft.com/en-us/aspnet/core/signalr/introduction?view=aspnetcore-8.0">SignalR</a>. SignalR is a .NETopen-source library that enables sending server-side notifications to client applications (e.g. web clients).</p> <p>The hub is implemented and configured simply as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FleetManagementHub</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Hub</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token function">SendFuelEfficiencyAlert</span><span class="token punctuation">(</span><span class="token class-name">IHubContext<span class="token punctuation">&lt;</span>FleetManagementHub<span class="token punctuation">></span></span> hubContext<span class="token punctuation">,</span> <span class="token class-name">FuelEfficiencyAlert</span> alert<span class="token punctuation">)</span> <span class="token operator">=></span> hubContext<span class="token punctuation">.</span>Clients<span class="token punctuation">.</span>All<span class="token punctuation">.</span><span class="token function">SendAsync</span><span class="token punctuation">(</span><span class="token string">"FuelEfficiencyAlertRaised"</span><span class="token punctuation">,</span> alert<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re also using:</p> <ul> <li><a href="https://www.npgsql.org/">Npgsql</a>, an Open Source Postgres provider for .NET,</li> <li><a href="https://github.com/NetTopologySuite/NetTopologySuite">NetTopologySuite</a> a GIS solution for .NET</li> <li>their plugins <em>Npgsql.NetTopologySuite</em> and <em>NetTopologySuite.IO.GeoJSON4STJ</em> packages to handle geometry types and their (de)serialisation.</li> </ul> <p>If you think that’s a lot of plumbing, then look from a different angle that all of those tools are integrating easily with each other and building a great ecosystem of Open Source tooling. You can build a complex solution based on that without the struggle. It also shows the power of Postgres and proves its maturity.</p> <p>Now the simple Web App subscribing to Postgres notifications and pushing it forward through SignalR can look as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Encodings<span class="token punctuation">.</span>Web</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">System<span class="token punctuation">.</span>Text<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Serialization</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Microsoft<span class="token punctuation">.</span>AspNetCore<span class="token punctuation">.</span>SignalR</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">NetTopologySuite<span class="token punctuation">.</span>IO<span class="token punctuation">.</span>Converters</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Npgsql</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">PostgresForDotnetDev<span class="token punctuation">.</span>Api</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">PostgresForDotnetDev<span class="token punctuation">.</span>Api<span class="token punctuation">.</span>Core</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token class-name">JsonOptions</span> <span class="token operator">=</span> <span class="token class-name">Microsoft<span class="token punctuation">.</span>AspNetCore<span class="token punctuation">.</span>Http<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>JsonOptions</span><span class="token punctuation">;</span> <span class="token comment">// tell Npgsql that we're using GIS coordinates</span> NpgsqlConnection<span class="token punctuation">.</span>GlobalTypeMapper<span class="token punctuation">.</span><span class="token function">UseNetTopologySuite</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">geographyAsDefault</span><span class="token punctuation">:</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Enable CORS for local web app</span> builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddCors</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span><span class="token function">AddPolicy</span><span class="token punctuation">(</span><span class="token string">"ClientPermission"</span><span class="token punctuation">,</span> policy <span class="token operator">=></span> <span class="token punctuation">{</span> policy <span class="token punctuation">.</span><span class="token function">WithOrigins</span><span class="token punctuation">(</span><span class="token string">"http://localhost:3000"</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AllowAnyMethod</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AllowAnyHeader</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AllowCredentials</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// configure serialisation of GeoJSON</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Configure</span><span class="token punctuation">(</span><span class="token class-name">JsonSerializerOptions</span> serializerOptions<span class="token punctuation">)</span> <span class="token punctuation">{</span> serializerOptions<span class="token punctuation">.</span>Encoder <span class="token operator">=</span> JavaScriptEncoder<span class="token punctuation">.</span>UnsafeRelaxedJsonEscaping<span class="token punctuation">;</span> serializerOptions<span class="token punctuation">.</span>NumberHandling <span class="token operator">=</span> JsonNumberHandling<span class="token punctuation">.</span>AllowNamedFloatingPointLiterals<span class="token punctuation">;</span> serializerOptions<span class="token punctuation">.</span>Converters<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">GeoJsonConverterFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> serializerOptions<span class="token punctuation">.</span>Converters<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">JsonStringEnumConverter</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Configure</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>JsonOptions<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>o <span class="token operator">=></span> <span class="token function">Configure</span><span class="token punctuation">(</span>o<span class="token punctuation">.</span>SerializerOptions<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Configure</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Microsoft<span class="token punctuation">.</span>AspNetCore<span class="token punctuation">.</span>Mvc<span class="token punctuation">.</span>JsonOptions<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>o <span class="token operator">=></span> <span class="token function">Configure</span><span class="token punctuation">(</span>o<span class="token punctuation">.</span>JsonSerializerOptions<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Add Postgres Subscription</span> builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddHostedService</span><span class="token punctuation">(</span>serviceProvider <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> logger <span class="token operator">=</span> serviceProvider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ILogger<span class="token punctuation">&lt;</span>BackgroundWorker<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> hubContext <span class="token operator">=</span> serviceProvider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IHubContext<span class="token punctuation">&lt;</span>FleetManagementHub<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BackgroundWorker</span><span class="token punctuation">(</span> logger<span class="token punctuation">,</span> ct <span class="token operator">=></span> FuelEfficiencyAlertsPostgresSubscription<span class="token punctuation">.</span><span class="token function">SubscribeAsync</span><span class="token punctuation">(</span> builder<span class="token punctuation">.</span>Configuration<span class="token punctuation">.</span><span class="token function">GetConnectionString</span><span class="token punctuation">(</span><span class="token string">"Postgres"</span><span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">,</span> hubContext<span class="token punctuation">,</span> ct <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Add SignalR</span> builder<span class="token punctuation">.</span>Services<span class="token punctuation">.</span><span class="token function">AddSignalR</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> app <span class="token operator">=</span> builder<span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">UseCors</span><span class="token punctuation">(</span><span class="token string">"ClientPermission"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">UseAuthorization</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">UseRouting</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>app<span class="token punctuation">.</span>Environment<span class="token punctuation">.</span><span class="token function">IsDevelopment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> app<span class="token punctuation">.</span><span class="token function">UseSwagger</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseSwaggerUI</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// map SignalR</span> app<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">MapHub</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>FleetManagementHub<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token string">"/hubs/fleet-management"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h2 id="getting-notifications-on-react-web-app" style="position:relative;"><a href="#getting-notifications-on-react-web-app" aria-label="getting notifications on react web app permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Getting notifications on React web app</h2> <p>And why not complete that with a <a href="https://www.youtube.com/watch?v=1EOf2D1dLbY">quadruple axel</a>? Let’s <a href="https://create-react-app.dev/">create a React app</a>:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">npx create-react-app fleet-management --template typescript</code></pre></div> <p>Add <a href="https://www.npmjs.com/package/@microsoft/signalr">SignalR npm package</a>:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">npm install @microsoft/signalr</code></pre></div> <p>You can also add a bit of <a href="https://tailwindcss.com/docs/guides/create-react-app">styling with Tailwind</a>.</p> <p>Then if you replace your <em>App.tsx</em> code with:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token string">"./tailwind.css"</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token string">'./App.css'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> useEffect<span class="token punctuation">,</span> useState <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"react"</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> HttpTransportType<span class="token punctuation">,</span> HubConnectionBuilder<span class="token punctuation">,</span> LogLevel <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@microsoft/signalr"</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">FuelEfficiencyAlert</span> <span class="token operator">=</span> <span class="token punctuation">{</span> vehicleId<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> startTime<span class="token operator">:</span> Date<span class="token punctuation">;</span> endTime<span class="token operator">:</span> Date<span class="token punctuation">;</span> fuelEfficiency<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">FleetManagementApp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">[</span>alerts<span class="token punctuation">,</span> setAlerts<span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token generic-function"><span class="token function">useState</span><span class="token generic class-name"><span class="token operator">&lt;</span>FuelEfficiencyAlert<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">useEffect</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// kids, don't do that on prod, be better and use https</span> <span class="token keyword">const</span> connection <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HubConnectionBuilder</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">configureLogging</span><span class="token punctuation">(</span>LogLevel<span class="token punctuation">.</span>Debug<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">withUrl</span><span class="token punctuation">(</span><span class="token string">"http://localhost:5000/hubs/fleet-management"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> skipNegotiation<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> transport<span class="token operator">:</span> HttpTransportType<span class="token punctuation">.</span>WebSockets <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">withAutomaticReconnect</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> connection<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">"fuelefficiencyalertraised"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>alert<span class="token operator">:</span> FuelEfficiencyAlert<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> alert<span class="token punctuation">.</span>startTime <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>alert<span class="token punctuation">.</span>startTime<span class="token punctuation">)</span><span class="token punctuation">;</span> alert<span class="token punctuation">.</span>endTime <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>alert<span class="token punctuation">.</span>endTime<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">setAlerts</span><span class="token punctuation">(</span><span class="token punctuation">(</span>prevAlerts<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">[</span><span class="token operator">...</span>prevAlerts<span class="token punctuation">,</span> alert<span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> connection<span class="token punctuation">.</span><span class="token function">start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">catch</span><span class="token punctuation">(</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>err<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> connection<span class="token punctuation">.</span><span class="token function">stop</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"mx-auto max-w-5xl px-6 py-4"</span><span class="token operator">></span> <span class="token operator">&lt;</span>h1 className<span class="token operator">=</span><span class="token string">"text-3xl font-bold mb-4"</span><span class="token operator">></span>Fleet Management App<span class="token operator">&lt;</span><span class="token operator">/</span>h1<span class="token operator">></span> <span class="token punctuation">{</span>alerts<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span> <span class="token operator">?</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"text-lg"</span><span class="token operator">></span>There are no alerts at the moment<span class="token punctuation">.</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"grid grid-cols-3 gap-4"</span><span class="token operator">></span> <span class="token punctuation">{</span>alerts<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>alert<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span> <span class="token operator">&lt;</span>div key<span class="token operator">=</span><span class="token punctuation">{</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>alert<span class="token punctuation">.</span>vehicleId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>alert<span class="token punctuation">.</span>startTime<span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">}</span> className<span class="token operator">=</span><span class="token string">"bg-white rounded-lg shadow p-4"</span> <span class="token operator">></span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"text-lg font-bold mb-2"</span><span class="token operator">></span> Alert <span class="token keyword">for</span> Vehicle <span class="token punctuation">{</span>alert<span class="token punctuation">.</span>vehicleId<span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"text-sm mb-2"</span><span class="token operator">></span> Start Time<span class="token operator">:</span> <span class="token punctuation">{</span>alert<span class="token punctuation">.</span>startTime<span class="token punctuation">.</span><span class="token function">toLocaleString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"text-sm mb-2"</span><span class="token operator">></span> End Time<span class="token operator">:</span> <span class="token punctuation">{</span>alert<span class="token punctuation">.</span>endTime<span class="token punctuation">.</span><span class="token function">toLocaleString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token operator">&lt;</span>div className<span class="token operator">=</span><span class="token string">"text-sm"</span><span class="token operator">></span>Fuel Efficiency<span class="token operator">:</span> <span class="token punctuation">{</span>alert<span class="token punctuation">.</span>fuelEfficiency<span class="token punctuation">}</span><span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token operator">&lt;</span><span class="token operator">/</span>div<span class="token operator">></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">default</span> FleetManagementApp<span class="token punctuation">;</span></code></pre></div> <p><strong>Et voilà! We just made our alerts from the database into the UI. All without resource-consuming polling and using fancy but practical Postgres features!</strong></p> <p>I hope this article shows you how extensible and powerful Postgres is and that it can give you a lot of fun and real help to deliver your business features in production-grade quality!</p> <p>Watch also more and see full considerations in the <a href="https://www.architecture-weekly.com/p/webinar-10-postgresql-superpowers">webinar recording</a>.</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/2oUow8SWVaI?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>Want to see full code? Check my repositories:</p> <ul> <li><a href="https://github.com/oskardudycz/postgres-for-dotnet-dev">Postgres for .NET developer</a></li> <li><a href="https://github.com/oskardudycz/PostgresOutboxPatternWithCDC.NET">Postgres Outbox Pattern with CDC and .NET</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Event stores are key-value databases, and why that matters]]>https://event-driven.io/en/event_stores_are_key_value_stores/https://event-driven.io/en/event_stores_are_key_value_stores/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e03ee592796deb290705fa72888717da/a331c/2023-04-07-es1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7CAAAOwgEVKEqAAAACQklEQVQozxXP2W6bQABAUb69L/2ASn2qWlWu00ZZmyhSkjZuHKfEYDA2BmNszL4M2wwzw2Kw1VT5gnsuQ6uWVC3EFcJVWbf7f6+WG/y8vpEV7YUTh0/s+eU1L8zHkylIIKk7TJuybmndFrRhSNkgUkNc5UVp2p7nR7wgHZ+cHf04Obu4ur1/6H//cXp+2ft2JKu6aft+lBblDtMG04aBpElQVZQdom2YYpARkBFcH3KyI80hQRWiHa73OWnSogoShGgLcgpJ81ZO4tiQOW+j2ppkKqK3VjdzPjC0zWzsGUtTnTra3F7J6xnnbzVzMYksXZfGzkouSM2AKBIeb1XhRRz9VviRMnkWHm/XM04Y3isTdvJ4K7MDlR+JwzuFH/GDm5U05gY3K5F9Y2OM55LgeY6qzB17G4axpm8hhDNpEvieE8QZquKcGFaQ5Fg3HFJ3K8OOUkSqlsmsEeDep0oPTD8UyscYOFaEKRB99l2mX7xw4jPLmU4QgBTRxomS5vBqe2GS47fnGPjAXfr2yrfUwFy4M2k5HKWm7hsSsHVFVreGFQbANN3IsJfDUeFHphtkOcZ0x6Cyg7Tjp4snVljIK/Dwx+/3keHQ3Suh3cYK44zGGdHsCAhT9/OnlON1N05gicsdU+AakyaFJMlxBkkKsjRKXNvT15avK9tfX8O/pxF3Zdx/Sbdq6MfpdmkMerF0h1DBmAC5AYC4rHZ7Wrek7ki9V7Q1J8q2Ps+444w/jfmLlO1Df0PbAww24LkXTy7zHP4HVKM7kCh9kWwAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e03ee592796deb290705fa72888717da/a331c/2023-04-07-es1.png" srcset="/static/e03ee592796deb290705fa72888717da/36ca5/2023-04-07-es1.png 200w, /static/e03ee592796deb290705fa72888717da/a3397/2023-04-07-es1.png 400w, /static/e03ee592796deb290705fa72888717da/a331c/2023-04-07-es1.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Event stores are the foundational building block of Event Sourcing. They’re also one of the biggest sources of confusion.</strong> They are databases turned upside down, append-only logs. A new event always comes at the end of it.</p> <p><strong>That’s their power, but it also may be their weakness, especially in the eyes of the people at the first steps of their journey.</strong> It’s pretty easy to think that an append-only log is just a pipe; something comes in on one side and felling out eventually from the other. This sounds already awfully close to messaging, isn’t it?</p> <p><strong>Having immutable data can be beneficial for many use cases.</strong> And here’s the thing: correlation is not the same as causation. For messaging, immutability is a foundational aspect of guaranteeing that message won’t be malformed in the transport; the less we do with it, the more performant solution we get. For the database, it means you won’t have random updates, deadlocks and other stuff that are killing database performance.</p> <p><strong>And I recently realised that even though event stores are implemented most of the time as global append-only logs technically, it’s better to explain them as <a href="/en/key-value-stores/">key-value stores</a>.</strong> Why?</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/82fa442db328a3adc632cbcb1b12b28e/a331c/2023-04-07-es2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7CAAAOwgEVKEqAAAAB+UlEQVQozzWRW08UQRCF5+f7aODFhMQQEMQlPnh5IZAYkUQM7q57QS7K4Lrs9HRXd1dXd1f3zK4mZhCSeqlTdZJTXxWBW6RkiTWG2VxIsF8Ho+2dvd2Xvb3e64PDD1vbO7t7veebW+Pp5bxSQlnnE4XkQi6MY+0YfbKU5hX8mouFgEWtK2UF4FzAQmilqVJ2tlCzO1lrZ4iti+hTgZTQZxcapBA5h5iQukEg5tBEnxyxweBdzNxyzI6S9cm6aCkVGBL6LgMoGE8vh+OLu0WF6K9G54PR+dmXPkjjNJZX5eDb9x/XJVmPISN1YQuKDYVMsQVQV6ebZf+FEr+Jlzdng8n+/vXxCVpPvimHJ5M367PhMVEiblzIndmF7P6bVW2mG2b8TFU3yEs7nureK/P51FoylvHiBI+e6tGhxehifoh9Dywal2pRwXjDTjegKo1v5Gii3r7TZ30ABMNq+skcrcHoCIC6mylanwrrs+2YNSCF6K/XgzUtbpGSAVQ2gCYLaDCrycfq/ZO6f2BtIG7IJ9fR9un/n6RU9Wwi5xOlpLYBFlIrBAEAaIyXt6W8HNblT63pkTYXd+CE1JbYc+v4D8YVPlBoKGaK2cWudbyk9NenFcV7WsSdeaZQaUMhp2bFueWm5bxMecmPlfIqNatOSW230IkrzsuY2n82QVTl9ixlOgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/82fa442db328a3adc632cbcb1b12b28e/a331c/2023-04-07-es2.png" srcset="/static/82fa442db328a3adc632cbcb1b12b28e/36ca5/2023-04-07-es2.png 200w, /static/82fa442db328a3adc632cbcb1b12b28e/a3397/2023-04-07-es2.png 400w, /static/82fa442db328a3adc632cbcb1b12b28e/a331c/2023-04-07-es2.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In event stores, 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.</p> <p>If we realise that, then it’s much more apparent!</p> <h2 id="is-event-store-a-messaging-tool" style="position:relative;"><a href="#is-event-store-a-messaging-tool" aria-label="is event store a messaging tool permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><strong>Is event store a messaging tool?</strong></h2> <p><a href="/en/event_streaming_is_not_event_sourcing/">Nope, it’s a database, just like any other.</a> It has built-in notification capabilities about new events, but that’s a perk, not the essence.</p> <h2 id="is-event-sourcing-performant-as-relational-or-document-databases" style="position:relative;"><a href="#is-event-sourcing-performant-as-relational-or-document-databases" aria-label="is event sourcing performant as relational or document databases permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><strong>Is Event Sourcing performant as relational or document databases?</strong></h2> <p>They’re in the same sense as denormalised documents with nested collections or as relational databases if you join data. If you consider the stream data as a list of events, they’re as performant as the total size. Just like in the document databases, if you put too much in its data, then performance will degrade. Same if you eagerly load more data in relational databases.</p> <h2 id="what-if-streams-get-too-long" style="position:relative;"><a href="#what-if-streams-get-too-long" aria-label="what if streams get too long permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><strong>What if streams get too long?</strong></h2> <p>If streams are the records, how many operations will you have, e.g. shopping cart? Of course, I’m not trying to downplay the issue. It exists. But it mostly comes from the fact that we don’t apply the modelling techniques and don’t include the lifetime aspect (e.g. accounting month instead of the whole account history or cashier shift instead of all transactions). Each databases types has its modelling techniques. We need to learn to use them effectively to optimise our usage. In Event Sourcing, <a href="https://www.youtube.com/watch?v=gG6DGmYKk4I">we should keep our streams short</a>.</p> <h2 id="do-we-get-strong-guarantees" style="position:relative;"><a href="#do-we-get-strong-guarantees" aria-label="do we get strong guarantees permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><strong>Do we get strong guarantees?</strong></h2> <p>I mentioned to you that it’s a key-value store, aye? So depending on the implementation, you should expect the same sort of guarantees as from the other databases: atomic writes, <a href="/en/optimistic_concurrency_for_pessimistic_times/">optimistic concurrency</a>, reading your own writes, etc.</p> <h2 id="should-i-share-event-store-between-microservices" style="position:relative;"><a href="#should-i-share-event-store-between-microservices" aria-label="should i share event store between microservices permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><strong>Should I share event store between microservices?</strong></h2> <p>Should you share a database between microservices?</p> <h2 id="do-i-need-to-load-all-events-or-can-i-use-snapshots" style="position:relative;"><a href="#do-i-need-to-load-all-events-or-can-i-use-snapshots" aria-label="do i need to load all events or can i use snapshots permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><strong>Do I need to load all events, or can I use snapshots?</strong></h2> <p>If you’re using MongoDB, are you loading the whole document before making your business decision or using a snapshot?</p> <h2 id="is-event-sourcing-a-global-architecture-concept" style="position:relative;"><a href="#is-event-sourcing-a-global-architecture-concept" aria-label="is event sourcing a global architecture concept permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><strong>Is Event Sourcing a global architecture concept?</strong></h2> <p>Is document database a global architecture concept? Event Sourcing is focused on capturing business facts that happened in your system and using them to make new decisions and capture the new facts based on them. It’s mostly focused on business logic and durability.</p> <h2 id="how-do-i-publish-events-between-microservices" style="position:relative;"><a href="#how-do-i-publish-events-between-microservices" aria-label="how do i publish events between microservices permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a><strong>How do I publish events between microservices?</strong></h2> <p>We already agreed that it’s not messaging tooling but a database. How do you publish events from a relational database? <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox</a>, CDC, etc. Event stores have built-in subscriptions, and you can use them to republish events forward, but logically it’s no different from any other database type. Moreover, you should also <a href="/en/events_should_be_as_small_as_possible/">differentiate internal and external events</a> to avoid leaking abstractions. Sharing internal events with other services is just like sharing the database tables between them.</p> <p><strong>You see where it’s going. We can continue those discussions, but I hope you see that it’s already liberating.</strong> I think that assuming that, logically, event stores are a special type of key-value store makes reasoning much easier. At least my tries to explain it like that in <a href="/en/training/">my workshops</a> seem to confirm my assumption.</p> <p>What are your thoughts? Does such an explanation make it more accessible for you?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Interested more on how event stores work internally? Read:</p> <ul> <li><a href="/en/relational_databases_are_event_stores/">What if I told you that Relational Databases are in fact Event Stores?</a>,</li> <li><a href="/en/lets_build_event_store_in_one_hour/">Let’s build event store in one hour!</a>,</li> <li><a href="/en/key-value-stores/">Unobvious things you need to know about key-value stores</a>,</li> <li><a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>.</li> </ul> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[ChatGPT, revolution or not?]]>https://event-driven.io/en/chat_gpt_revolution_or_not/https://event-driven.io/en/chat_gpt_revolution_or_not/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/eb3608be37e78079e9dbe76972051ed7/a331c/2023-04-02-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAADJElEQVQozwEZA+b8ADwtIXZkUfLWr+jOqePNq+TNp/bSnOC2gHZjUTY8Qjg3P2JMSrh/U7RkQ2NJRXlrWcmnfa+Vco57YYFrVQA0Jh2Cb1n937Tu0afky6Xxz57uyJPctIKVeV1UTEdNQ067fV3/yVnpYR6ISkFST0mahGatkm6TfGGBbFcAJhoUaFhI9das7c2ezLaS8MyX4LyNzKuBvZtygXFegmpjw35i/7xP/6Iny1UnW09LY1lKoYZmjXdegm9ZADAhG3lnVezPqd/AmauagtW1js6sg7ybdaiKaHZnVINpW8+NUb2HNK54LNdnFWVENkhCOXJgTW9gUGtdTQAvJSFdUESbinaZiHWGe2yWiHKXhW2OfWd9alRbRTJVQTJcQy1EMCM7KCA/Kx03JhwxJB0zKiQ7NjI5NTEAHxsfOzApTUQ+XFNJbGBSbGFUcmZXc2ZXcGBPY1RDX1BBVkc7Sj0yPjMpMikkMSkkNCslLCYiLikmNTAtACYZFzAlITYoIlc5LlVDN2NUR2VYSXFdS3liSm9PPGVMOnBXQGNLNllCL045KUg3KkM1KjkkHzIoIjs0LwBHLR9CIRcxGBIyHxc+JRtSOy07JiBiRjNuTzdYNSpHLyNvTTRVOCdRNiVbOSZULh1TLyJhOyo+HxU2HxkAKBYPJxAKGQ0MCwcNMB8VTzosGhgbQy0hVTooHyIiKSAaTTYnQiMaKh8abE0zTysTb0YzjUcyJQQDLhsTAAkHDgUEDRUQDxkHCiIUESQaFxELDScbFjwrHysUDy0bFTgpHhcSEjAgF3xbNj4jFU08L1wlGxECCCUSDQAJBg0UCw0SDQ4MAgoEAgwQCw8BBQ4bEhI+KyANDBAXCxA4Jx4TEBE8JRdnSi0qFQ8QBwgyBwYPBAoRBgkAEAsQDQkMBAUOEQ0SDQoRFxETEAwSPSsea0wuTzwmaUksaUstSDEgMB4XNSIYJBIOGAcJIAQGDAMJCAMJAAsMEgYGEAsKEBgSFRkTFCIZFyIXF0gyJWRHLHlYNINgN5BmOJRpOWlJKlc7JEctHDYZESgRDRoMDBUJDNMN4QoflaxLAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/eb3608be37e78079e9dbe76972051ed7/a331c/2023-04-02-cover.png" srcset="/static/eb3608be37e78079e9dbe76972051ed7/36ca5/2023-04-02-cover.png 200w, /static/eb3608be37e78079e9dbe76972051ed7/a3397/2023-04-02-cover.png 400w, /static/eb3608be37e78079e9dbe76972051ed7/a331c/2023-04-02-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="/en/about/">I started my career when StackOverflow didn’t exist.</a> That’s how I quite often introduce myself. I’m that old.</p> <p>That’s, of course, nothing spectacular, as many people more experienced than me would roll out their eyes hearing me. Still, it gives me perspective on our coding industry before that.</p> <p>Those were dark days. Getting quick help with the coding problem you’re trying to solve was almost impossible. Now, does your notebook have a CD player? Nope, if you were a .NET developer, you installed the whole Microsoft Documentation from CD. Or best, you have to read books, but there was no Kindle. Webinars? Free content from <a href="/en/revolution_now/">Developers Advocates</a>? Forget about it.</p> <p>Still, when we look at our stories from the trenches and the battles we somehow survived, they look pretty romantic from our current standpoint. <em>Hey boy, see we were wounded, but we’re still standing!</em>. We tend to believe that others must go through those trenches to become as experienced and remarkable as we did.</p> <p>Some may say that <em>no pain, no gain</em>. And that’s the hard truth; you learn best if you feel the pain. You want to avoid it. Still, does it really have to hurt?</p> <p>When StackOverflow had its hay days, I heard <em>“Now we won’t need to code anything, just copy/paste and call it a day!”</em>.</p> <p>Or some more dramatic: <em>“We won’t be needed anymore!”</em>.</p> <p><strong>Somehow, that didn’t happen.</strong> Did our work change? Sure? Is it better or worse? It’s different.</p> <p>It appeared that StackOverflow is a helpful tool but not perfect. It’s useful for quick and narrowed problems but not great for getting complete design guidance. The answers are not always correct; we must evaluate them and do due diligence. Also, how people interact is not the most efficient, and losing people to people’s touch is not the most efficient.</p> <p><strong>Does that sound familiar?</strong></p> <p>We’re getting into the hype peak of AI-related tooling like <a href="https://chat.openai.com">ChatGPT</a>. Some people believe that programmers won’t be needed anymore. Some say that’s the most revolutionised thing since we invented the wheel. Where’s the truth? As always: in the middle.</p> <p><strong>I needed a code to be the starting point for my new <a href="/en/training/">workshop</a>, “Event Sourcing on production”. I struggled to create code for the starting phase showing the common architecture/implementation flaws.</strong> I was even considering paying some junior to write such. I found a solution when I started to use ChatGPT. The code it generates is a perfect match for such needs!</p> <p>Jokes aside, the tool is impressive, and I firmly believe it’s a must-have nowadays as it speeds up boilerplate work enormously. Yet, as StackOverflow isn’t a solution for all of our development pains, ChatGPT and related tooling won’t be.</p> <p><strong>Copilot, ChatGPT et al. are great for giving the initial solution for narrowed questions. They won’t solve (yet?) or invent a solution for complex, abstract problems.</strong> Why? Let’s briefly look at how it works.</p> <blockquote> <p>“The GPT-4 rumor mill is a ridiculous thing. I don’t know where it all comes from. (…) People are begging to be disappointed and they will be. The hype is just like… We don’t have an actual AGI and that’s sort of what’s expected of us.”</p> </blockquote> <p>Who’s quote is that? <a href="https://www.theverge.com/23560328/openai-gpt-4-rumor-release-date-sam-altman-interview">Sam Altman, CEO of OpenAI, ChatGPT creators</a>.</p> <p><strong>What’s AGI? Per Wikipedia:</strong></p> <blockquote> <p>“Artificial general intelligence (AGI) is the ability of an intelligent agent to understand or learn any intellectual task that human beings or other animals can.”</p> </blockquote> <p>So if those tools are not AGI, then what are they? GPT are <a href="https://en.wikipedia.org/wiki/Large_language_model">large language models</a>. You can consider them a huge graph, where nodes are words, and connections are probabilities.</p> <p>Taking two nodes and the probability between them will express how likely one word will come after another. It also takes specific context considerations and other words you are more likely to use.</p> <p>That’s why massive input data is critical for such models. The more we put into them to train them, the bigger number of contentions they have, the more they can answer.</p> <p><strong>So, e.g. if you asked them, <em>“Who’s the president?”</em>.</strong> Then it’ll probably tell you the name of the USA president, as US-based companies would most likely put the most information having it. So that’s the most probable answer.</p> <p>If you add <em>“Who’s the President of Poland?”</em> it’ll probably tell you the name of the President of Poland. Will it tell you the current one? Probably, as our current one is eight years in the position, so it should be in the trained data.</p> <p>If you ask, <em>“Who’s the president of the Polish Programmers Association?”</em> then it’ll either tell you it doesn’t know or <em>make up</em> the answer. Actually, it’s not making up. It checks the connections, and let’s say it has the minimum threshold of giving the answer defined as, e.g. 20%; if it finds something above the threshold, it’ll take the most probable explanation. If not, then it’ll say that it doesn’t know.</p> <p><strong>Of course, it’s a plain English explanation from a non-native speaker. No one knows precisely how those tools work, even the authors.</strong> And <a href="https://fortune.com/2023/03/17/sam-altman-rivals-rip-openai-name-not-open-artificial-intelligence-gpt-4/">OpenAI is not so open on their work</a>. For more educated explanation, read a great post by <a href="https://writings.stephenwolfram.com/2023/02/what-is-chatgpt-doing-and-why-does-it-work/">Stephen Wolfram - What Is ChatGPT Doing … and Why Does It Work?</a>.</p> <p><strong>And here are a few dangers.</strong> Getting made-up answers or worse, <a href="https://www.washingtonpost.com/technology/2023/03/30/midjourney-ai-image-generation-rules/">images or videos</a> is an obvious threat.</p> <p>I explained some dangers in <a href="/en/computer_says_no_we_may_have_an_issue_with_ai_soon/">Computer says no! Why we might have an issue with Artificial Intelligence soon</a>. Those models are as objective as the people who trained them and tuned their parameters.</p> <p><strong>We’ll need to learn to use it cautiously and ethically, as IT people teach others about that.</strong> It’s a big concern, especially since it doesn’t seem to be the highest priority for the companies. Prove? <a href="https://techcrunch.com/2023/03/13/microsoft-lays-off-an-ethical-ai-team-as-it-doubles-down-on-openai">Microsoft just layed off an ethical AI team</a>.</p> <p>We should also get the tools to help us fight those fakes or detect AI-generated stuff. That won’t be easy, but the need will be so strong that I expect this arms race to start soon.</p> <p>The other is privacy. OpenAI and similar companies are not telling us what training set they used. We don’t know if what we get respects the copyrights or laws like <a href="/en/gdpr_for_busy_developers">GDPR</a>. <a href="https://www.zerohedge.com/technology/italy-bans-openais-chatgpt-over-privacy-concerns">Italy already blocked ChatGPT</a> for that.</p> <p><strong>Yet, those are <em>just</em> tools. You can use a knife to put butter on the bread or stab someone (but please, don’t).</strong></p> <p>And that’s okay; we need to adapt and find a good way of using this tool. I’m considering it a chance to focus on what’s important.</p> <p><strong>Working with ChatGPT reminded me to pair coding with a skilled coder but a bad programmer.</strong> You will get decent results if you drive it well, but it won’t set architecture for you. It’s a tool that reminds me of <a href="https://www.imdb.com/title/tt0209144/">Memento</a> plot. A guy with a lot of old memories, who remembers what happened a minute ago but forgets what we talked 10m ago.</p> <p>It’s great for providing the initial draft or solution for a focused case or brainstorming. Yet, beware, and remember how it works.</p> <p><strong>Most of the stuff we can find on the Internet is mediocre.</strong> If we ask ChatGPT about something, we’ll probably get a mediocre answer because they repeat the most often in the training data. We can narrow it down, give more context and get a more detailed and correct answer, but we need to know what to ask for.</p> <p>Knowing what we want to achieve is the most important, ChatGPT et al. can help us to get answers on how to do that. But it’s our role to decide which answer is correct, as they’re <em>only</em> the most probable. A mediocre solution can be good enough for everyday tasks, but for advanced or the most important? Probably not. We’ll still need to put more work into that.</p> <p><strong>Okay, then how it helped me to build the initial state for the <a href="/en/training/">training</a>?</strong> See the current result in the repo: <a href="https://github.com/oskardudycz/event-sourcing-on-prod-workshop/">https://github.com/oskardudycz/event-sourcing-on-prod-workshop/</a>.</p> <p>Even with its help, it took me a few days to prepare and fix its issues. But without its help, it’d taken me even more. Yet, watch out! Discussions with it and authoring may take longer than if you just did it on your own.</p> <p><strong>Another example? I recently did a <a href="https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates">webinar for Architecture Weekly subscribers</a> and asked ChatGPT for inspiration about the example of a complex DDD aggregate.</strong> I used it as the base to show how you could refactor it and slim them down.</p> <p>It generated something that was <em>okayish</em>; I’ve seen such code in other codebases, but definitely not the best way to design aggregates. Also, to be sure that it’ll generate what I wanted, I had to give such precise commands:</p> <blockquote> <p>“Could you give me a real-world example of the complex DDD aggregate? Describe the business rules and invariants. It should have at least seven properties, several business rules and invariants showing business logic.</p> <p>It should have a list of nested data and one entity.</p> <p>Root aggregate should represent a complex workflow or process. It should express lifetime or state transition and have several methods for running business logic.</p> <p>Use some random, uncommon, business use case fitting the above requirement (other than Order to not get us bored).</p> <p>Describe aggregate flow, business rules first and invariants before implementation.</p> <p>Don’t list or describe properties; just express them in code. Provide an example implementation using C# 11.</p> <p>Start with the main aggregate implementation, then put the rest of the entities’ definitions.</p> <p>Write the whole code, including business logic and invariant checks. Provide aggregate and entities as separate code snippets.</p> <p>Remember that entities should also be broken into dedicated snippets.”</p> </blockquote> <p>Watch what happened next:</p> <p><a href="https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABf0lEQVQoz43STW+bMADGcXACCUk00h2WqtqkbredVyAhjg0Yv2ACKcRm0bpVTQ+77DPs0k9S7ZNOLOlb0sPQ7/j8ZSTbQHGBkxLFBcQ5xBJimdBLwuqIrFC8hFjOUWs3I7TCcamb75XMBcuNVOgd+k/KlSyvKn17qW4qfVvrrSyvmNzQrN0QrlKhE7qOmUqYMghf77FWQus2VttKbYvquqxvZPGtLbl6XKZCtXgbqwMpX6e8RmQFoYBQzBZyES0TWh8vj2Khzz99dt23tmX/+fXj/uemAzrOYBiElGZfE7Z+OkPowzgV+vTs48Bx7E73rma/C2wYhmma/oxQ+SJmWfNKPDk7Hw6G4/EJsPom6Np2DwDgT/8rbt5N3vd7vfEbd+A4vX6/a9ldyzo+mWbN01XtLyxrJqcfRqOR67oAAPPh86eE5Zvdr+3wfGOgKH8Ox0UImedFnh97wd4XD0PcvpNFtHwUkZVxEeDn/Gk8RyJEIlzw2YMQiWCeHiwvAvwXwuai7bWJjV4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/7af8471e0a882fd79aeae4792d7e8e58/a331c/2023-04-02-webinar.png" srcset="/static/7af8471e0a882fd79aeae4792d7e8e58/36ca5/2023-04-02-webinar.png 200w, /static/7af8471e0a882fd79aeae4792d7e8e58/a3397/2023-04-02-webinar.png 400w, /static/7af8471e0a882fd79aeae4792d7e8e58/a331c/2023-04-02-webinar.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p><strong>So, TLDR. Don’t be afraid of AI-related tooling.</strong> Learn how to use them, as they can speed up your work and give you a competitive advantage over the people that don’t. Yet, it would be best to understand how they work and what they can provide you.</p> <p>I hope this article gives you some hints and considerations to help you do it consciously.</p> <p><strong>Play with it, experiment and have fun!</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Of course, I used AI to generate cover of this blog post.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Ensuring uniqueness in Marten event store]]>https://event-driven.io/en/unique_constraint_in_marten_event_store/https://event-driven.io/en/unique_constraint_in_marten_event_store/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c5a43f3f18feab2bc79d19ea881895bc/a331c/2023-03-24-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAADHklEQVQozyXL7U8SAQDH8fs3ep8rVy+qNWfZm5bZk7q1HtTKredMNDV8GIFi5lCboJyK+ACphDxImhcIKoKg3qGUD6SCHE/5cOLJgxAeYlvXsO23z35vvoB1IwibzAgMj42oBvt7HVjAiRMoFkKxsB0L27fix7Gz7/KGv0pEGiWEwPAMPGtdD6A7BKCSfQKrivtaPi4heqiTpR7oQ3HCjoXXtkL/Z8dCjt3o2JD0a/uHJUQnbmdzK4uV/V2LVhfQUV3IKnvdyCiZNWhXf8xoRNxprcrlO7Rt7tk2g9bNPZf/ENaPq3ubVsxT5hk9u4paV07hVeVrPoNAfxO9rYqikvYYRgYnlfLWOubH0mdzsMG5S9i29px41DyLcCpecmsZekhmUA+q5L3t1W8+N9JUHUxAK2RCLWXGYZF9ETEoZYpuLrvsecvb7FZu8yoWaW/jgdQcdtlTeWeTHpLYF5FpZT/UWq4VMMc7K4BlBcMkyLOOcJ0IZNF9mZS2dn8oAatLVaM6FCfUWmNLTXnn+yKdBFyaUDgQyKZpMwnylhUMI/85YOTcnOel+6c43jkpAgnNxlHL8op1w+8J/rFvhz3BP7bNwM+V1e/T4wgkxEySIAwu8DONnBvaxkxggJowwbow35W1IKOvWuY9IdLpP0DxyJo3jO78jotHnP4DT4i0LlsW5cz5rmxdXcoANWGIngRwc48J8k+oWWmzgte6wR5P8C+6E89QPILi+0dG7N6wZ4/UD4tN3fmj9deFlJPNucfkFUkA/e5pavpxXn7yJJ8iZuVZ1jyewKEDjzjwfQdOHBlxB2IrTkxcR5nkUzoKL1LTE2h3TvELLwMNr66W3j7LepzcR8sAC9IM+oltgnTvEm5f1OWLun1R9y6xvU/CMzBYcE1Ey2h4coF6+0z9i1Tem6sAWJD2/lES/d4ZWU2O8N39bwoJHiM3ArH1QGzdH3cjEMNjpHp4SEDLGqh9yMg6y3xwHixIa351Cah/llKUntjw8oqkOkvBKRF18bcj5Lr/IF4e+csX9RKkuOeTnF0ircluzEstupVY/zSlMvvcP6MsFYAtkQ93AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/c5a43f3f18feab2bc79d19ea881895bc/a331c/2023-03-24-cover.png" srcset="/static/c5a43f3f18feab2bc79d19ea881895bc/36ca5/2023-03-24-cover.png 200w, /static/c5a43f3f18feab2bc79d19ea881895bc/a3397/2023-03-24-cover.png 400w, /static/c5a43f3f18feab2bc79d19ea881895bc/a331c/2023-03-24-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Unique constraint validation is one of those things that looks simple but is not always easy</strong>. <a href="/en/uniqueness-in-event-sourcing/">I explained already that, in Event Sourcing, it may be challenging</a>.</p> <p>In <a href="https://martendb.io/">Marten</a>, we’re trying to show the pragmatic face of Event Sourcing and embrace that whether something is best practice or anti-pattern depends on the context. We also understand that things can often get rougher, and you may get into a situation where you need to take a tradeoff and work on the proper design as the next step. <strong>The trick I will present in this article is precisely in this category.</strong></p> <p><strong>Let’s say that we’d like to enforce the uniqueness of the user email.</strong> In theory, you could query a read model keeping all users’ data and check if the user with the selected email already exists. Easy peasy? It sounds like it, but it’s not. Such an approach will give you a false guarantee. In the meantime, between you got the result, ran your business logic and stored new user information, someone could have added a user with the same email. Querying is never reliable, <a href="/en/tell_dont_ask_how_to_keep_an_eye_on_boiling_milk/">we should tell, not ask</a>. So, in this case, claim the user email end either succeed or fail.</p> <p><strong>If we had a relational database, we could set a unique constraint and call it a day.</strong> And hey, in Marten, that’s what we have under the cover: rock-solid Postgres.</p> <p>Marten stores all events on the same table. Each event is a separate row, and data is placed in the JSONB column. <a href="https://www.postgresql.org/docs/current/datatype-json.html#JSON-INDEXING">Postgres allows indexing of JSON data</a>. Such indexes are performant but insufficient to index the whole events table. Yet, there’s another way!</p> <p><strong>Marten allows running inline projections. They run in the same transaction as appending events.</strong> <a href="https://martendb.io/documents/sessions.html#unit-of-work-mechanics">Marten has a built-in unit of work</a>. Pending changes are wrapped in database transactions and stored together when we call <em>SaveChanges</em>. When we append a new event, Marten looks for all registered inline transactions that can handle this event type. For each of them, it runs the apply logic. Projection results are stored in a separate table for the specific document type as <a href="https://martendb.io/documents/">Marten document</a>. (Read more in <a href="/en/projections_in_marten_explained/">Event-driven projections in Marten explained</a>).</p> <p><strong>We can use the read model and define a unique constraint on it.</strong> If the read model is updated in the same transaction as the event append, the event won’t be appended when the unique constraint fails.</p> <p>Let’s say that we have the following events:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">UserCreated</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> UserId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Email <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">UserEmailUpdated</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> UserId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Email <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">UserDeleted</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> UserId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Of course, typically, we’ll have more event types; I provided only those related to setting, updating or removing user email.</p> <p><strong>We could define the following read model with projection:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">UserNameGuard</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Email <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserNameGuardProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">SingleStreamProjection<span class="token punctuation">&lt;</span>UserNameGuard<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">UserNameGuardProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token generic-method"><span class="token function">DeleteEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserDeleted<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">UserNameGuard</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">UserCreated</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>UserId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Email<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">UserNameGuard</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">UserEmailUpdated</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">UserNameGuard</span> guard<span class="token punctuation">)</span> <span class="token operator">=></span> guard <span class="token keyword">with</span> <span class="token punctuation">{</span> Email <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Email <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Now, if we register this projection we can also define the unique constraint in <em>DocumentStore</em> registration:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> store <span class="token operator">=</span> DocumentStore<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserNameGuardProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Schema<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserNameGuard<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">UniqueIndex</span><span class="token punctuation">(</span>guard <span class="token operator">=></span> guard<span class="token punctuation">.</span>Email<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><a href="https://martendb.io/documents/indexing/unique.html">Unique Index provides more customisation</a>. Let’s take an example of cinema ticket reservations. We could define a condition to ensure we have only a single <strong>active</strong> reservation for the specific seat.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> store <span class="token operator">=</span> DocumentStore<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> options<span class="token punctuation">.</span>Schema<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Reservation<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Index</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>SeatId<span class="token punctuation">,</span> x <span class="token operator">=></span> <span class="token punctuation">{</span> x<span class="token punctuation">.</span>IsUnique <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token comment">// Partial index by supplying a condition</span> x<span class="token punctuation">.</span>Predicate <span class="token operator">=</span> <span class="token string">"(data ->> 'Status') != 'Cancelled'"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>That’s neat, isn’t it?</strong> Check also <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Marten.Integration.Tests/EventStore/UniqueConstraint/UniqueContstraintTests.cs">full sample</a>.</p> <p><strong>Of course, with great power comes great responsibility.</strong> From my experience, a unique constraint requirement is usually a sign that our <a href="/en/bring_me_problems_not_solutions/">business people bring us a solution instead of the problem</a>. We should ensure that what we’re trying to provide here is a real requirement and <a href="https://en.wikipedia.org/wiki/Five_whys">ask enough whys</a> before we commit to it.</p> <p>Still, sometimes we need to select the hill we’d like today for, or just need to do our stuff. This recipe should be treated as a tradeoff, but it should take you pretty far if used cautiously.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If you liked this article, also check <a href="/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/">a simple trick for idempotency handling in the Elastic Search read model</a>.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Projecting Marten events to Elasticsearch]]>https://event-driven.io/en/projecting_from_marten_to_elasticsearch/https://event-driven.io/en/projecting_from_marten_to_elasticsearch/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/762b0d58d7d96f31ba9dbb610689d428/a331c/2023-03-18-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAAByklEQVQoz2PYu7Jtz5ZZt2+f//fv3384+Pfv39+//wkBhknZyktzVTvjNRf05d29c+7zp9fff/2E6/uL1wiGPecndvQ4RNtK6QkzhJmLdCYZd+e7bpxf9ejeKbAT8Nq8advk+HBlNwcFbR1RAyU2I0kGa2kGe3mGIFOhiY3RF68fg3gCu+Y3b17cuH/u+bu7N+9dOXv+8MrV06bPLDOzlDdU4Sv25Y5xkbl7/wou9zOg8f/+/fP////lG6YamglkV+j52IkeO7EXLo6u+R8E/Ae57O+//3/+/P7///+Hz+86Juct3zzt+NkDnz9/RHP5379/IVy4zX///zz7/9ed/6QAhv//QJ75fH3B26MaH+9G/v/35cHDx7du3Xr37v2zZ0+vX7/+6tXrV69evX79+s2bNy9fvvzw4cOFCxfevHkDtfnf//9Jkf7qmXVup75+/falpbl5zpw5M2fOnDhxYn9//4oVK+bOnTt58uSurq6+vr5t27bNmDHjwIEDIM1/wa6fde60+JZ1zhfPf/z969+fP9+RwI8fP759+/bjx4+vX79+//79169fP3/+/PPnz79//6B+/vP//8kvH+59/0IoXWCLqn//yQEA50idiVNnP88AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/762b0d58d7d96f31ba9dbb610689d428/a331c/2023-03-18-cover.png" srcset="/static/762b0d58d7d96f31ba9dbb610689d428/36ca5/2023-03-18-cover.png 200w, /static/762b0d58d7d96f31ba9dbb610689d428/a3397/2023-03-18-cover.png 400w, /static/762b0d58d7d96f31ba9dbb610689d428/a331c/2023-03-18-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong><a href="/en/projections_and_read_models_in_event_driven_architecture/">I told you already</a> that Projections are an Event Sourcing killer feature, and today I’d like to repeat that.</strong></p> <p>In <a href="https://martendb.io/">Marten</a>, we embraced that and <a href="/en/projections_in_marten_explained/">provide various ways of handling projections</a>. We hugely benefit from Postgres shapeshifter capabilities. It’s a highly versatile database. Even though relational usage is its primary use case, it can quickly become an <a href="https://martendb.io/events/">event store</a>, <a href="https://martendb.io/documents/">document</a>, <a href="https://age.apache.org/">graph</a>, or <a href="https://www.timescale.com/">time series</a> database.</p> <p><strong>Projecting straight into Postgres gives you enough power for most common cases. Yet, sometimes you need to do more.</strong> Nowadays, we can get razor-focused solutions for our scenarios with emerging types of various <a href="/en/key-value-stores/">key/value databases</a>. Marten also allows you to get benefits and integrate with them.</p> <p>I explained already <a href="/en/integrating_Marten/">how to integrate Marten with another tooling</a>. <strong>Today, I’ll show you how to build in practice external projection, using Elasticsearch as an example.</strong></p> <p>Here are our assumptions:</p> <ul> <li>even though <a href="https://martendb.io/documents/full-text.html">Postgres has full-text search capabilities</a>, we’d like to use Elasticsearch as it’s a database natively built for such needs.</li> <li>as Elasticsearch has eventual consistency, plus we don’t want to fall into <a href="https://martinfowler.com/articles/patterns-of-distributed-systems/two-phase-commit.html">Two Phase Commit</a> issue, we’ll use asynchronous processing.</li> <li>we’d like not to introduce additional messaging tooling to need to apply events stored in Marten.</li> <li>we won’t expect massive traffic, so assume that <a href="https://martendb.io/events/projections/async-daemon.html">Marten’s built-in asynchronous projections handling</a> will be able to keep the pace.</li> <li>Marten will guarantee sequential order of event processing and that all events will be delivered.</li> </ul> <p><strong>OK, enough talking. Talk is cheap. Let me show you some code!</strong> To build Marten’s custom projection, we must implement <em>IProjection</em>. We’ll define a base class to reuse later in defining the specific projection.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">ElasticsearchProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IProjection</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IDocumentOperations</span> operations<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streams<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NotImplementedException</span><span class="token punctuation">(</span><span class="token string">"We don't want to do 2PC, aye?"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span> <span class="token class-name">IDocumentOperations</span> operations<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streamActions<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellation <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// TODO: We'll get to that!</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ElasticsearchClient</span> ElasticsearchClient <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">abstract</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> IndexName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token keyword">virtual</span> <span class="token return-type class-name">Task</span> <span class="token function">SetupMapping</span><span class="token punctuation">(</span><span class="token class-name">ElasticsearchClient</span> client<span class="token punctuation">)</span> <span class="token operator">=></span> client<span class="token punctuation">.</span>Indices<span class="token punctuation">.</span><span class="token function">CreateAsync</span><span class="token punctuation">(</span>IndexName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">HashSet<span class="token punctuation">&lt;</span>Type<span class="token punctuation">></span></span> handledEventTypes <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> handledEventTypes<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">TEvent</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Implementing the <em>IProjection</em> interface forces us to implement two Apply methods, one for .NET asynchronous processing and the other for synchronous.</strong> We don’t expect our projection to be run inline (in the same transaction as the event append), so we can skip synchronous variant implementation.</p> <p>We can also express that in registration helper:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ElasticsearchProjectionConfig</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TElasticsearchProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">ProjectionOptions</span> projectionOptions<span class="token punctuation">,</span> <span class="token class-name">ElasticsearchClient</span> client <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TElasticsearchProjection</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ElasticsearchProjection</span><span class="token punctuation">,</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span></span> <span class="token operator">=></span> projectionOptions<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TElasticsearchProjection</span> <span class="token punctuation">{</span> ElasticsearchClient <span class="token operator">=</span> client <span class="token punctuation">}</span><span class="token punctuation">,</span> ProjectionLifecycle<span class="token punctuation">.</span>Async <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see, we’re also injecting <em>ElasticsearchClient</em>. We’re not doing that via the constructor; thanks to that, we won’t need to implement the constructor each time for derived projection.</p> <p>We also added the possibility to define the basic ElasticSearch settings, like the index name and mapping configuration to which we’ll project our events.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">protected</span> <span class="token keyword">abstract</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> IndexName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token keyword">virtual</span> <span class="token return-type class-name">Task</span> <span class="token function">SetupMapping</span><span class="token punctuation">(</span><span class="token class-name">ElasticsearchClient</span> client<span class="token punctuation">)</span> <span class="token operator">=></span> client<span class="token punctuation">.</span>Indices<span class="token punctuation">.</span><span class="token function">CreateAsync</span><span class="token punctuation">(</span>IndexName<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We also defined the possibility of specifying event types that we’d like to use in our projection logic:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">HashSet<span class="token punctuation">&lt;</span>Type<span class="token punctuation">></span></span> handledEventTypes <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> handledEventTypes<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">TEvent</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Let’s use those options and define our general events apply logic.</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span> <span class="token class-name">IDocumentOperations</span> operations<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streamActions<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellation <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> existsResponse <span class="token operator">=</span> <span class="token keyword">await</span> ElasticsearchClient<span class="token punctuation">.</span>Indices<span class="token punctuation">.</span><span class="token function">ExistsAsync</span><span class="token punctuation">(</span>IndexName<span class="token punctuation">,</span> cancellation<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>existsResponse<span class="token punctuation">.</span>Exists<span class="token punctuation">)</span> <span class="token keyword">await</span> <span class="token function">SetupMapping</span><span class="token punctuation">(</span>ElasticsearchClient<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> events <span class="token operator">=</span> streamActions<span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>streamAction <span class="token operator">=></span> streamAction<span class="token punctuation">.</span>Events<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token operator">=></span> handledEventTypes<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>EventType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span>ElasticsearchClient<span class="token punctuation">,</span> events<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token keyword">virtual</span> <span class="token return-type class-name">Task</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span><span class="token class-name">ElasticsearchClient</span> client<span class="token punctuation">,</span> <span class="token class-name">IEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> events<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span> client<span class="token punctuation">,</span> events<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token operator">=></span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">virtual</span> <span class="token return-type class-name">Task</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span><span class="token class-name">ElasticsearchClient</span> client<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> events<span class="token punctuation">)</span> <span class="token operator">=></span> Task<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">;</span></code></pre></div> <p><strong>Marten internally does a lot of performance optimisations in asynchronous processing.</strong> Each projection is processed in parallel, and events are batched to enable reduced network traffics with batch loads and updates. That’s perfect, as it’s also best practice while working with Elasticsearch to process changes in batches!</p> <p>The first step is to ensure that the Elasticsearch index exists and has mappings defined. Elasticsearch can create automapping, but it’s recommended to set it up explicitly for production usage.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> existsResponse <span class="token operator">=</span> <span class="token keyword">await</span> ElasticsearchClient<span class="token punctuation">.</span>Indices<span class="token punctuation">.</span><span class="token function">ExistsAsync</span><span class="token punctuation">(</span>IndexName<span class="token punctuation">,</span> cancellation<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>existsResponse<span class="token punctuation">.</span>Exists<span class="token punctuation">)</span> <span class="token keyword">await</span> <span class="token function">SetupMapping</span><span class="token punctuation">(</span>ElasticsearchClient<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then we select only the events from the batch that we’d like to process.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> events <span class="token operator">=</span> streamActions<span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>streamAction <span class="token operator">=></span> streamAction<span class="token punctuation">.</span>Events<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token operator">=></span> handledEventTypes<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>EventType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span>ElasticsearchClient<span class="token punctuation">,</span> events<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then we call the overload method. It is the method we intend to overload for our projection. It reduces the need for a boilerplate and gives us enough data to proceed with handling.</p> <p><strong>Let’s say that we have the following events representing the Order flow:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">OrderInitiated</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> OrderId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> OrderNumber<span class="token punctuation">,</span> <span class="token class-name">UserInfo</span> User <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">OrderShipmentAddressAssigned</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> OrderId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> ShipmentAddress <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">OrderCompleted</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> OrderId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> OrderNumber<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> UserName <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">UserInfo</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> UserName <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We want to project it to the following document:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Order</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> OrderNumber<span class="token punctuation">,</span> <span class="token class-name">UserInfo</span> User<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Status<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> ShipmentAddress <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We could define projection as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ElasticsearchProjection</span></span> <span class="token punctuation">{</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> IndexName <span class="token operator">=></span> <span class="token string">"Document"</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">OrderProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderInitiated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderShipmentAddressAssigned<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderCompleted<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name">Task</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span><span class="token class-name">ElasticsearchClient</span> client<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// (...) TODO</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’d need to fill the <em>ApplyAsync</em> logic. It gives us the best option for customisation and performance optimisation. But it’s still tedious if we’d always like to load and update documents.</p> <p><strong>Wouldn’t it be better if it looked like that?</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ElasticsearchProjection<span class="token punctuation">&lt;</span>Order<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">OrderProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">DocumentId</span><span class="token punctuation">(</span>o <span class="token operator">=></span> o<span class="token punctuation">.</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderInitiated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> Apply<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderShipmentAddressAssigned<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> Apply<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderCompleted<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> Apply<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">Order</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">Order</span> order<span class="token punctuation">,</span> <span class="token class-name">OrderInitiated</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> order <span class="token keyword">with</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> OrderNumber <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>OrderNumber<span class="token punctuation">,</span> User <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>User <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name">Order</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">Order</span> order<span class="token punctuation">,</span> <span class="token class-name">OrderShipmentAddressAssigned</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> order <span class="token keyword">with</span> <span class="token punctuation">{</span> ShipmentAddress <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ShipmentAddress <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name">Order</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">Order</span> order<span class="token punctuation">,</span> <span class="token class-name">OrderCompleted</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> order <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> <span class="token string">"Completed"</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>How to achieve it?</strong> We need to add another base class. Let’s start with basic registration for the event handlers and document information.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">ElasticsearchProjection<span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">></span></span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ElasticsearchProjection</span></span> <span class="token keyword">where</span> <span class="token class-name">TDocument</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">record</span> <span class="token class-name">ProjectEvent</span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span> GetId<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">,</span> TDocument<span class="token punctuation">></span></span> Apply <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> IndexName <span class="token operator">=></span> IndexNameMapper<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ToIndexName</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Type<span class="token punctuation">,</span> ProjectEvent<span class="token punctuation">></span></span> projectors <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span> getDocumentId <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span> getId<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TDocument<span class="token punctuation">></span></span> apply <span class="token punctuation">)</span> <span class="token punctuation">{</span> projectors<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">TEvent</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProjectEvent</span><span class="token punctuation">(</span> @<span class="token keyword">event</span> <span class="token operator">=></span> <span class="token function">getId</span><span class="token punctuation">(</span><span class="token punctuation">(</span>TEvent<span class="token punctuation">)</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>document<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">apply</span><span class="token punctuation">(</span>document<span class="token punctuation">,</span> <span class="token punctuation">(</span>TEvent<span class="token punctuation">)</span>@<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">DocumentId</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span> documentId<span class="token punctuation">)</span> <span class="token operator">=></span> getDocumentId <span class="token operator">=</span> documentId<span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name">Task</span> <span class="token function">SetupMapping</span><span class="token punctuation">(</span><span class="token class-name">ElasticsearchClient</span> client<span class="token punctuation">)</span> <span class="token operator">=></span> client<span class="token punctuation">.</span>Indices<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">CreateAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>opt <span class="token operator">=></span> opt <span class="token punctuation">.</span><span class="token function">Index</span><span class="token punctuation">(</span>IndexName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">GetDocumentId</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> projectors<span class="token punctuation">[</span>@<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">GetId</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>For each event, we need to provide two lambdas:</p> <ul> <li>one for getting the document id from event data. We’ll use it to load documents to update.</li> <li>second, for providing the logic of updating document data based on event data.</li> </ul> <p>I’m also using <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core.ElasticSearch/Indices/IndexNameMapper.cs">convention-based index mapping</a> based on the Document type.</p> <p><strong>Now, the <em>Grand Finale</em>! Let’s define the Apply logic.</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span><span class="token class-name">ElasticsearchClient</span> client<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> ids <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>GetDocumentId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> searchResponse <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">SearchAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>s <span class="token operator">=></span> s <span class="token punctuation">.</span><span class="token function">Index</span><span class="token punctuation">(</span>IndexName<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Query</span><span class="token punctuation">(</span>q <span class="token operator">=></span> q<span class="token punctuation">.</span><span class="token function">Ids</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">IdsQuery</span> <span class="token punctuation">{</span> Values <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Ids</span><span class="token punctuation">(</span>ids<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingDocuments <span class="token operator">=</span> searchResponse<span class="token punctuation">.</span>Documents<span class="token punctuation">.</span><span class="token function">ToDictionary</span><span class="token punctuation">(</span>ks <span class="token operator">=></span> <span class="token function">getDocumentId</span><span class="token punctuation">(</span>ks<span class="token punctuation">)</span><span class="token punctuation">,</span> vs <span class="token operator">=></span> vs<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> updatedDocuments <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">Apply</span><span class="token punctuation">(</span>existingDocuments<span class="token punctuation">.</span><span class="token function">GetValueOrDefault</span><span class="token punctuation">(</span>ids<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function">GetDefault</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> bulkAll <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">BulkAll</span><span class="token punctuation">(</span>updatedDocuments<span class="token punctuation">,</span> SetBulkOptions<span class="token punctuation">)</span><span class="token punctuation">;</span> bulkAll<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Data indexed"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token keyword">virtual</span> <span class="token return-type class-name">TDocument</span> <span class="token function">GetDefault</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> ObjectFactory<span class="token operator">&lt;</span>TDocument<span class="token operator">></span><span class="token punctuation">.</span><span class="token function">GetDefaultOrUninitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name">TDocument</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">TDocument</span> document<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> projectors<span class="token punctuation">[</span>@<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>document<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">virtual</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">SetBulkOptions</span><span class="token punctuation">(</span><span class="token class-name">BulkAllRequestDescriptor<span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">></span></span> options<span class="token punctuation">)</span> <span class="token operator">=></span> options<span class="token punctuation">.</span><span class="token function">Index</span><span class="token punctuation">(</span>IndexName<span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre></div> <p>The first step is to get all the document ids from events and query Elasticsearch to get all existing documents having them.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> ids <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>GetDocumentId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> searchResponse <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">SearchAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TDocument<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>s <span class="token operator">=></span> s <span class="token punctuation">.</span><span class="token function">Index</span><span class="token punctuation">(</span>IndexName<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Query</span><span class="token punctuation">(</span>q <span class="token operator">=></span> q<span class="token punctuation">.</span><span class="token function">Ids</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">IdsQuery</span> <span class="token punctuation">{</span> Values <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Ids</span><span class="token punctuation">(</span>ids<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingDocuments <span class="token operator">=</span> searchResponse<span class="token punctuation">.</span>Documents <span class="token punctuation">.</span><span class="token function">ToDictionary</span><span class="token punctuation">(</span>ks <span class="token operator">=></span> <span class="token function">getDocumentId</span><span class="token punctuation">(</span>ks<span class="token punctuation">)</span><span class="token punctuation">,</span> vs <span class="token operator">=></span> vs<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Having that, we can run the apply logic by providing the existing document (or the new default one) and the event as inputs.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> updatedDocuments <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">,</span> i<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">Apply</span><span class="token punctuation">(</span>existingDocuments<span class="token punctuation">.</span><span class="token function">GetValueOrDefault</span><span class="token punctuation">(</span>ids<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function">GetDefault</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span></code></pre></div> <p>We’re calling the Apply method that takes the registered lambdas based on the event type:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">private</span> <span class="token return-type class-name">TDocument</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">TDocument</span> document<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> projectors<span class="token punctuation">[</span>@<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>document<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>As a result, we’re getting new versions of the documents. We’re storing all of them using <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html">Elasticsearch bulk capabilities</a> to make them efficient and reliable (that’s also why we provide the option to customise bulk option).</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> bulkAll <span class="token operator">=</span> client<span class="token punctuation">.</span><span class="token function">BulkAll</span><span class="token punctuation">(</span>updatedDocuments<span class="token punctuation">,</span> SetBulkOptions<span class="token punctuation">)</span><span class="token punctuation">;</span> bulkAll<span class="token punctuation">.</span><span class="token function">Wait</span><span class="token punctuation">(</span>TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">5</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token string">"Data indexed"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Thanks to that, we’re getting reliable and efficient processing of the documents. Of course, best if we don’t have to do the load and save roundtrips but just run updates. Still, not always that’s achievable. This combination of base classes should give you enough flexibility to define your own optimised projection handling.</p> <p><strong>You can define projection to your other favourite storage type using the presented pattern (e.g. MongoDB, Neo4J, etc.)</strong> The most important is not to end up with the lowest common denominator and use the best practices related to the particular storage. Marten’s batching capabilities and ordering and delivery guarantees should give you enough power and flexibility to do it efficiently.</p> <p>See detailed implementation in <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/212">my sample repo</a>. Try it on your own and have fun!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If you liked this article, also check <a href="/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/">a simple trick for idempotency handling in the Elastic Search read model</a>.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to handle multiple commands in the same transaction]]>https://event-driven.io/en/simple_transactional_command_orchestration/https://event-driven.io/en/simple_transactional_command_orchestration/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ed2689fc47b2797d7047edfa25d9b150/a331c/2023-03-12-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAAsTAAALEwEAmpwYAAADJElEQVQozwEZA+b8AJGPj5KRkqemqpWRkmNYVZiSksfHzsLCyMjK0ZR9e4JYT1RLSAQEBgwLDTEvMkE9PAcJFAAKJxknSZ+hrACZl5eqqK2zsrYkISFFDwR4SkDAwcfIx87S0tjW2N/O0Nbc3OJoaGsAAAAKCQomJCUFBxoACis8SWmurbMAh4ODdXFxPzs7SB4XjjQor3Vw0dTb0M/Uvb7CycnO4uPo2Nje5eXsdXV4BwgJFw8NQi85AAssanWQu7m9ABEJBggCAAAAAFIgF34lGLKYl97j56aioAkIByAdHbCus9PU2ayqrq+vtH18fhULDoJMRg4TMHuEmbCtrwABAAALBQMEAgIEAQAYAwA+ODdQTUtDPToDAAALCQicmZlpZmZmYmKkpal/amgJAgoUHjkVKVR4f5GIhYIABgoUAQAABQMCAQEBAAAAJyUld3N0rammZ2VjAAAAgX16vby+w8TL4eLoramuBwkcDx5BFShSnKK1t7S3AAMECwQBAAoFAmdmaFZVWAoIB4OAgJWRjTIpJBoTDQkJCKmnqczM08zK0KOmsQIKJgoWMw4hSpCYrLOwsQABAgIAAAA2MC3U09eLi5AAAABuaWiTjIdLPjiwnIlmW1R3b2q8ur+/v8WSlaIAByEGDysADTF4gZm3tLcAAAAAAAAAEg4MmJCIraaeGhoYJRsWcGNaqJ+UvKuXxLyzjoZ8mJGKnJWLeXh5AAcgGiVCAAsoTVVqkId9AAAAAQMEBwAAAExHPrywnGJbUhYTELWomKebi6mYhKWaipmNfI+EdY2AcFZLRAAADTY4QAUSLz9IXpSIdgABAw0FCRUAAABOLianlYOWj4BnVku1pJCelYetnoufl4mOhnuPiH9tYlk2KiQhFhA9IBkQAQdKR02Ti4MAAAAIAgIIAAECeE5Hu4Z7mI5+l4VzrJmGoY+CrJ6NrqOXn5WJnJSInZaKlY6DXkxCVDgsUD82b2dgjIN+ACAYEgsHBSUhHGtYUKRjVa+KbIpuU6OPeK6YhaqWgNTAqL2mjIuBdIqAd4Z8c4B4cHl0aYB6cIR9d4N7dUzHLA8Cwn61AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ed2689fc47b2797d7047edfa25d9b150/a331c/2023-03-12-cover.png" srcset="/static/ed2689fc47b2797d7047edfa25d9b150/36ca5/2023-03-12-cover.png 200w, /static/ed2689fc47b2797d7047edfa25d9b150/a3397/2023-03-12-cover.png 400w, /static/ed2689fc47b2797d7047edfa25d9b150/a331c/2023-03-12-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Let’s say that we’re starting a new project.</strong> It’s a small tool for internal needs, maybe even some sort of <a href="https://en.wikipedia.org/wiki/Shadow_IT">shadow IT</a> project. It may also be the project that won’t have massive traffic and won’t need to scale significantly. We’re more caring about the delivery time, solving the exact issue. So for our case, consistency may also be a more important factor. Yes, contrary to common belief presented in the conference talks, such projects are still run.</p> <p><strong>Take project management, for example, Jira-like software.</strong> We’d like to create a workspace for specific company departments where they can manage their projects. Our business wants to have the default project created during workspace creation. That can cut out some keystrokes and make the setup more accessible.</p> <p>Is this example dumb? Maybe, as it may seem, as the typical <em>buy-it-from-the-shelve</em> option. Yet, the power is in the niche. I was once involved in building a product focused on project management for the construction industry. They have the specific needs to manage big <a href="https://en.wikipedia.org/wiki/Computer-aided_design">CAD</a> files and manage formal discussions on their changes. Building tools focused on solving a particular niche need may also be a correct decision, as more generic solutions won’t fulfil all the needs.</p> <p><strong>Nevertheless, the problem I’m going to explain is generic. <em>How to handle two commands</em> or <em>how to change two aggregates in one transaction</em> are one of those questions I’m asked most often.</strong></p> <p>The answer is always <em>it depends</em>. The classical event-driven way is to use <a href="/en/saga_process_manager_distributed_transactions/">asynchronous process</a> together with <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">outbox pattern</a>. <strong>Yet, in some cases, isn’t it too much?</strong></p> <p>Let’s say that the first result of our experimentation looks like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">ApiController</span></span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Route</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"[controller]"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WorkspaceController</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ControllerBase</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">WorkspaceController</span><span class="token punctuation">(</span><span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>documentSession <span class="token operator">=</span> documentSession<span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">HttpPost</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>ActionResult<span class="token punctuation">&lt;</span>WorkspaceDetails<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">Post</span><span class="token punctuation">(</span><span class="token class-name">CreateWorkspaceRequest</span> request<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> userId <span class="token operator">=</span> <span class="token function">GetUserId</span><span class="token punctuation">(</span>HttpContext<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> workspaceId <span class="token operator">=</span> MartenIdGenerator<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> cmd <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateWorkspace</span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">,</span> request<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> request<span class="token punctuation">.</span>TaskPrefix<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>workspaceCreatedEvent<span class="token punctuation">,</span> backlogCreatedEvent<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token function">Handle</span><span class="token punctuation">(</span>cmd<span class="token punctuation">)</span><span class="token punctuation">;</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">,</span> workspaceCreatedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>backlogCreatedEvent<span class="token punctuation">.</span>ProjectId<span class="token punctuation">,</span> backlogCreatedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> workspace <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LoadAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>WorkspaceDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>workspace <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Ok</span><span class="token punctuation">(</span>workspace<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s a simple ASP.NET controller that fulfils application layer needs. We’re using <a href="https://martendb.io/">Marten</a> to store the events defined by the business logic.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">WorkspaceService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token punctuation">(</span>WorkspaceCreated<span class="token punctuation">,</span> ProjectCreated<span class="token punctuation">)</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">CreateWorkspace</span> command<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> slug <span class="token operator">=</span> SlugGenerator<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>Name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> backlogId <span class="token operator">=</span> MartenIdGenerator<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">WorkspaceCreated</span><span class="token punctuation">(</span> <span class="token named-parameter punctuation">WorkspaceId</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>WorkspaceId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">Name</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> <span class="token named-parameter punctuation">TaskPrefix</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>TaskPrefix<span class="token punctuation">,</span> <span class="token named-parameter punctuation">Slug</span><span class="token punctuation">:</span> slug<span class="token punctuation">,</span> <span class="token named-parameter punctuation">CreatedById</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>CreatedById <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProjectCreated</span><span class="token punctuation">(</span> <span class="token named-parameter punctuation">ProjectId</span><span class="token punctuation">:</span> backlogId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">Name</span><span class="token punctuation">:</span> <span class="token string">"Backlog"</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">StartDate</span><span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">EndDate</span><span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">Status</span><span class="token punctuation">:</span> ProjectStatus<span class="token punctuation">.</span>Active<span class="token punctuation">,</span> <span class="token named-parameter punctuation">Slug</span><span class="token punctuation">:</span> <span class="token string">"backlog"</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">WorkspaceId</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>WorkspaceId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">CreatedById</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>CreatedById<span class="token punctuation">,</span> <span class="token named-parameter punctuation">IsBacklog</span><span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>If you’re not using Event Sourcing, C# or Marten, bear with me. What I will show will also apply to other logic that we need to orchestrate in a transactional way.</strong></p> <p>Getting back to our code, we’re not using aggregate or other fancy patterns, as our logic is simple enough; using function will be more than enough. We’re just taking command, generating some data and returning events that the workspace and project were created.</p> <p>The code generally looks alright, but it’s already a bit smelly. We see that once it evolves with upcoming requirements, it may not withstand the test of time.</p> <p><strong>Especially those three lines look suspicious:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">,</span> workspaceCreatedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>backlogCreatedEvent<span class="token punctuation">.</span>ProjectId<span class="token punctuation">,</span> backlogCreatedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It seems that our business logic generates two events that should be stored in different streams. We’re crossing the entity boundaries. Calling <em>SaveChangesAsync</em> will ensure that all of the changes will be stored or none. Consistency is guaranteed, but it seems wrong. Why?</p> <p><strong>Project and Workspace are separate things with different lifetimes, business rules, etc.</strong> If we keep the default project creation in <em>WorkspaceService</em> and add later on dedicated <em>ProjectService</em> for other operations (e.g. regular creation), then we’re not having a single source of truth.</p> <p>Let’s then move the project creation to a dedicated service. Our <em>ProjectService</em> may look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ProjectService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CreateBacklogProject</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProjectId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> WorkspaceId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> CreatedById <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProjectCreated</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">CreateBacklogProject</span> command<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProjectCreated</span><span class="token punctuation">(</span> <span class="token named-parameter punctuation">ProjectId</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>ProjectId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">Name</span><span class="token punctuation">:</span> <span class="token string">"Backlog"</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">StartDate</span><span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">EndDate</span><span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">Status</span><span class="token punctuation">:</span> ProjectStatus<span class="token punctuation">.</span>Active<span class="token punctuation">,</span> <span class="token named-parameter punctuation">Slug</span><span class="token punctuation">:</span> <span class="token string">"backlog"</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">WorkspaceId</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>WorkspaceId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">CreatedById</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>CreatedById<span class="token punctuation">,</span> <span class="token named-parameter punctuation">IsBacklog</span><span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Nothing fancy, but it’s already prepared for upcoming rules and methods specific to the project. <strong>Notice that we already embraced in the command name that backlog project may differ from a regular one.</strong> It is a small detail, but better not to generalise our business logic too early. We can always do that later when we understand the nuances of our domain better. We could also create a dedicated event for that, e.g. <em>BacklogProjectCreated</em> if we believe that’s different enough from the regular one.</p> <p>The <em>WorkspaceService</em> will look now as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">WorkspaceService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CreateWorkspace</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> WorkspaceId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> TaskPrefix<span class="token punctuation">,</span> <span class="token class-name">Guid</span> CreatedById <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">WorkspaceCreated</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">CreateWorkspace</span> command<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">WorkspaceCreated</span><span class="token punctuation">(</span> <span class="token named-parameter punctuation">WorkspaceId</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>WorkspaceId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">Name</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> <span class="token named-parameter punctuation">TaskPrefix</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>TaskPrefix<span class="token punctuation">,</span> <span class="token named-parameter punctuation">Slug</span><span class="token punctuation">:</span> SlugGenerator<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>Name<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">CreatedById</span><span class="token punctuation">:</span> command<span class="token punctuation">.</span>CreatedById <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Our business logic didn’t change much, but we already see explicitly that those are different operations:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> workspaceCreated <span class="token operator">=</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateWorkspace</span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">,</span> request<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> request<span class="token punctuation">.</span>TaskPrefix<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> backlogCreated <span class="token operator">=</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateBacklogProject</span><span class="token punctuation">(</span>backlogId<span class="token punctuation">,</span> workspaceId<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">,</span> workspaceCreated<span class="token punctuation">)</span><span class="token punctuation">;</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>backlogId<span class="token punctuation">,</span> backlogCreated<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>If we know already that we’ll need this <em>pattern</em> more often, we could also add a helper method to handle such scenarios.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CommandHandling</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token function">ComposeAsync</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token keyword">params</span> <span class="token punctuation">(</span>Guid<span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">)</span><span class="token punctuation">[</span><span class="token punctuation">]</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token keyword">var</span> <span class="token punctuation">(</span>streamId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token keyword">in</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>streamId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> documentSession<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>And use it as such:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">ComposeAsync</span><span class="token punctuation">(</span> <span class="token punctuation">(</span>workspaceId<span class="token punctuation">,</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateWorkspace</span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">,</span> request<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> request<span class="token punctuation">.</span>TaskPrefix<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>backlogId<span class="token punctuation">,</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateBacklogProject</span><span class="token punctuation">(</span>backlogId<span class="token punctuation">,</span> workspaceId<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>If we’d like to make this process explicit, we could even set up a Domain Service to orchestrate this process.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">WorkspaceService</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">ProjectService</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">WorkspaceCreationScenario</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token punctuation">(</span>WorkspaceCreated<span class="token punctuation">,</span> ProjectCreated<span class="token punctuation">)</span></span> <span class="token function">CreateWorkspace</span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span> generateId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> userId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> taskPrefix <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> workspaceId <span class="token operator">=</span> <span class="token function">generateId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> backlogId <span class="token operator">=</span> <span class="token function">generateId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span><span class="token punctuation">(</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateWorkspace</span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">,</span> name<span class="token punctuation">,</span> taskPrefix<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateBacklogProject</span><span class="token punctuation">(</span>backlogId<span class="token punctuation">,</span> workspaceId<span class="token punctuation">,</span> userId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre></div> <p>We can also define an explicit code to orchestrate that with storage:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">WorkspaceCreationScenarioHandler</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span> <span class="token function">CreateWorkspace</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> userId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> taskPrefix <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>workspaceCreated<span class="token punctuation">,</span> projectCreated<span class="token punctuation">)</span> <span class="token operator">=</span> WorkspaceCreationScenario<span class="token punctuation">.</span><span class="token function">CreateWorkspace</span><span class="token punctuation">(</span>MartenIdGenerator<span class="token punctuation">.</span>New<span class="token punctuation">,</span> userId<span class="token punctuation">,</span> name<span class="token punctuation">,</span> taskPrefix<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">ComposeAsync</span><span class="token punctuation">(</span> <span class="token punctuation">(</span>workspaceCreated<span class="token punctuation">.</span>WorkspaceId<span class="token punctuation">,</span> workspaceCreated<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>projectCreated<span class="token punctuation">.</span>ProjectId<span class="token punctuation">,</span> projectCreated<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> projectCreated<span class="token punctuation">.</span>WorkspaceId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then our controller method can look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">HttpPost</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>ActionResult<span class="token punctuation">&lt;</span>WorkspaceDetails<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">Post</span><span class="token punctuation">(</span><span class="token class-name">CreateWorkspaceRequest</span> request<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> workspaceId <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">CreateWorkspace</span><span class="token punctuation">(</span> <span class="token function">GetUserId</span><span class="token punctuation">(</span>HttpContext<span class="token punctuation">)</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> request<span class="token punctuation">.</span>TaskPrefix <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> workspace <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LoadAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>WorkspaceDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>workspaceId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>workspace <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Ok</span><span class="token punctuation">(</span>workspace<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Didn’t we just create more code?</strong> Yes, we did; what’s the reason, then?</p> <p>It could be helpful if your scenario evolves. Let’s say that besides the default project, you’d like to also:</p> <ul> <li>create a default project manager,</li> <li>send an email,</li> <li>setup some storage for project files,</li> <li>etc.</li> </ul> <p><strong>In that case, you might just need to change the parts that should be moving.</strong> It’ll only involve updating the orchestration code in Scenario by adding integration with another logic. You won’t need to touch other command handlers or API endpoint.</p> <p><strong>Of course, you need to make a sanity check.</strong> At some point, the write amplification may be too big. Modifying too many entities in the same transaction is asking youurself for concurrency conflicts and deadlocks. Asynchronous processing wasn’t invented just to make our life harder. At least, not only.</p> <p><strong>Still, we already made things explicit; we distilled the business logic from coordination and drawing boundaries.</strong> If it appears that we’re doing too much, we can replace our scenario with <a href="/en/saga_process_manager_distributed_transactions/">Saga or another asynchronous coordination style</a>. We focused on composition instead of adding new layers, as <a href="/en/generic_does_not_mean_simple/">generic does not mean simple</a>.</p> <p>You could notice that I didn’t include returning data in our scenario class. Why? I’m a huge fan of separating business logic from queries. A clear split of which classes are responsible for running business logic and querying makes code more predictable. We may decide to orchestrate that on the application layer and still return the data from the controller method, but we should keep the split layer down. I wrote longer on those considerations in <a href="/en/can_command_return_a_value/">Can command return a value?</a>.</p> <p><strong>What if we do one more step and isolate the data retrieval from command handling but still give the client application a similar experience?</strong> We could use <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location">Location header</a> for that. It tells where’s the newly created resource. The client will need to do one more call, but typically it’s not an issue as browsers, and most of the mature HTTP client libraries do it automatically after seeing <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/201">201 CREATED</a> status.</p> <p>How to do it? Simple as that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">ApiController</span></span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Route</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"[controller]"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WorkspaceController</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ControllerBase</span></span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">HttpPost</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>ActionResult<span class="token punctuation">&lt;</span>WorkspaceDetails<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">Post</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">CreateWorkspaceRequest</span> request <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> workspaceId <span class="token operator">=</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token function">CreateWorkspace</span><span class="token punctuation">(</span> <span class="token function">GetUserId</span><span class="token punctuation">(</span>HttpContext<span class="token punctuation">)</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> request<span class="token punctuation">.</span>TaskPrefix <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Created</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/workspace/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">workspaceId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> workspaceId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">HttpGet</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"{id:guid}"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>ActionResult<span class="token punctuation">&lt;</span>WorkspaceDetails<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">GetById</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">IQuerySession</span> querySession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> workspace <span class="token operator">=</span> <span class="token keyword">await</span> querySession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LoadAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>WorkspaceDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> workspace <span class="token keyword">is</span> <span class="token keyword">not</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token function">Ok</span><span class="token punctuation">(</span>workspace<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>It is also a good practice not to do redundant roundtrips, especially by default, assuming the client will need this data.</strong> Most of the time, the client already knows what they submitted. Of course, some data may be generated on the backend, but will the client immediately need them? We should always try to define the UI in that way to make the user flow the most effective, but we also take other considerations. Smart UX can also cut off a lot of complexity from the technical solution.</p> <p><strong>So, how far can you go with this pattern?</strong> There’s no obvious answer. Going down that path is always a tradeoff and potential <em>design smell</em>. We should be careful applying that and avoid getting too attached to it. I suggest using it as a pragmatic choice, a way to experiment and deliver business value.</p> <p><strong>All models are wrong, but some of them are useful.</strong> Don’t treat your model as set in stone but as a living thing that should evolve when you learn more about your business or requirements change.</p> <p>Build the code that’s explicit about intention and removable. I hope this article gives you a decent food for thought on how to achieve that.</p> <p>You can also check <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/209/commits">this PR and follow commits</a> to see the refactoring step by step.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Fun with serial JSON]]>https://event-driven.io/en/fun_with_json_serialisation/https://event-driven.io/en/fun_with_json_serialisation/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/db562488565c387c598f26eb8ce347b0/a331c/2023-03-05-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7CAAAOwgEVKEqAAAADJElEQVQozwEZA+b8AB0bFicoHSIkGCcrHC8xK1BUV2hud2Noc1hcZ1ZVV3ZuX3Z4fXB3gVFUV19kaWhud0JDTTc4PC8wLzc6MwAgIBoeHhgeHxcpLB8xMytOUVdudYByeINUWWU9MyrJs5aempB6g41jaGeJkJJ0eoJWWWE3N0MwLztAQzsAGRkRGhkSHBwVJCQdJiQkSkxTbnWAcnmCWV5qNS4psJl9zMe6dn98cHdzipOec3qFUVRaPT1KRklKR0tAACAfHCYoISIiHi0tLSknKEBARnB3gHN6hEhJTiIfHmVbUIWAea2ys5uospGcqXyEkVZZYEZHUz9BP0dNPQBAQkJDRUZFR0tRU1o+PkNRU1mAh49RUVEoJSMcGhwMDBEwKiaKf3XI0dPE09usuMBvdX1RU10yMTJITD0ATVJOV1xeUFNYQEJENDQ1QkRJTk9SKiEZOjg1KCUmIB0dQj06b2JXqZyM0t/ku8nRgoqSUFNbMi4rPDo1AE1TRklMSj9AQz0/OzExLz1AOi4qJSgfFzk1MSYkJB8dHjg0M2ZeVqSMc83QzMPT25WepWBiY01KSSwqLQBha1dRWUlISk5FSEY6PjRITD8vJB0hGhUkHhoeGxkeHBwyLy95a16ni228s6XJ2eGgqa9+gYBXWFo2NjsAiZt7aXVgXmRkS01SQEJDV1RSQjQoFhEMIh0YHRoXIR8gMCsrkX1oi3NZnot3v8zSm6WrhYqLU1VcPT5GAIaTjHuCgYqSl4KIkEdKVG5kWmNSQRsWETUtKBwaGC0sMz04OHlpWlFFOldIPJucm5iiqXx+gFBSW0RGUABiaHd0e4WSnKiIj5hVWF9OR0NTSUIgGhY9NTAlIh8nJy1LRUJxYlVeXVwsJSFyb2uRmp9+f4BOUFlCQ00Aoay0pa+3fISMUFNYNDU4Pz0/PT08GxcUU0xIKiUiGBYYOjUxW1NNhoyQQkJCU0tGgoeMgYOCUVJbP0BMAKq2vpGbo3uCjHV7hHl/iEhKTkRHShwaGT04NS4qKRENCzErJ11WUqSqrXZ7fTItKXFzeGdqa2Zqak1RVeanCngW+TCYAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/db562488565c387c598f26eb8ce347b0/a331c/2023-03-05-cover.png" srcset="/static/db562488565c387c598f26eb8ce347b0/36ca5/2023-03-05-cover.png 200w, /static/db562488565c387c598f26eb8ce347b0/a3397/2023-03-05-cover.png 400w, /static/db562488565c387c598f26eb8ce347b0/a331c/2023-03-05-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>JSON serialisation is so much fun. We can make jokes and curse, but we must live with it.</strong> Surprisingly, that’s not getting simpler if we use JavaScript or TypeScript. It may look simple as we have <em>JSON.parse</em> and <em>JSON.stringify</em> to make the mapping to text representation back and forth. That’s correct until we use more advanced types.</p> <p><strong>For JSON <em>Date</em> and <em>BigInt</em> are already too advanced.</strong> Both are not defined in the <a href="https://www.rfc-editor.org/rfc/rfc8259">JSON standard</a>. Date will be serialised to string and BigInt? Will fail with error… What to do if we have such fields?</p> <p>Let’s say that we have the following type of definition <a href="/en/type_script_node_Js_event_sourcing/">representing the Shopping Cart events</a>:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartCanceled'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> canceledAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’d like to have it typed and not need additional mapping in our business code. Still, based on what I wrote above, this will not (de)serialise correctly. What to do?</p> <p>The first option is to define <a href="">explicit type representing serialised payload</a>.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartEventPayload</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> PricedProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartCanceled'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> canceledAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Having it, we can define <a href="/en/explicit_events_serialisation_in_event_sourcing/">explicit mapping</a> like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> ShoppingCartEventSerde <span class="token operator">=</span> <span class="token punctuation">{</span> serialize<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token operator">:</span> ShoppingCartEvent<span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartEventPayload <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartOpened'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>data<span class="token punctuation">,</span> openedAt<span class="token operator">:</span> data<span class="token punctuation">.</span>openedAt<span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>data<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> data<span class="token punctuation">.</span>confirmedAt<span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartCanceled'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>data<span class="token punctuation">,</span> canceledAt<span class="token operator">:</span> data<span class="token punctuation">.</span>canceledAt<span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> deserialize<span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token operator">:</span> ShoppingCartEventPayload<span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartEvent <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartOpened'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>data<span class="token punctuation">,</span> openedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>openedAt<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>data<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>confirmedAt<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartCanceled'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token operator">...</span>data<span class="token punctuation">,</span> canceledAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>data<span class="token punctuation">.</span>canceledAt<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>And use it as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> serialisedJSON <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span>ShoppingCartEventSerde<span class="token punctuation">.</span><span class="token function">serialize</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> deserialised <span class="token operator">=</span> ShoppingCartEventSerde<span class="token punctuation">.</span><span class="token function">deserialize</span><span class="token punctuation">(</span><span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>serialisedJSON<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It may sound redundant, but we could also use it for more advanced mapping, e.g. to <a href="/en/simple_events_versioning_patterns/">version our events</a> to keep backward compatibility.</p> <p><strong>This pattern is also helpful in more generic scenarios, like handling Web API payload parsing.</strong></p> <p>What if you don’t want to define a specific class but have it more generic? Not an issue. You can use the following parser:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> JSONParser <span class="token operator">=</span> <span class="token punctuation">{</span> stringify<span class="token operator">:</span> <span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To <span class="token operator">=</span> From<span class="token operator">></span><span class="token punctuation">(</span> value<span class="token operator">:</span> From<span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> StringifyOptions<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To<span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">stringify</span><span class="token punctuation">(</span> options<span class="token operator">?.</span>map <span class="token operator">?</span> options<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>value <span class="token keyword">as</span> MapperArgs<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">:</span> value<span class="token punctuation">,</span> options<span class="token operator">?.</span>replacer <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> parse<span class="token operator">:</span> <span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To <span class="token operator">=</span> From<span class="token operator">></span><span class="token punctuation">(</span> text<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> ParseOptions<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To<span class="token operator">></span> <span class="token punctuation">)</span><span class="token operator">:</span> To <span class="token operator">|</span> <span class="token keyword">undefined</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> parsed<span class="token operator">:</span> <span class="token builtin">unknown</span> <span class="token operator">=</span> <span class="token constant">JSON</span><span class="token punctuation">.</span><span class="token function">parse</span><span class="token punctuation">(</span>text<span class="token punctuation">,</span> options<span class="token operator">?.</span>reviver<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>options<span class="token operator">?.</span>typeCheck <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>options<span class="token operator">?.</span><span class="token generic-function"><span class="token function">typeCheck</span><span class="token generic class-name"><span class="token operator">&lt;</span>To<span class="token operator">></span></span></span><span class="token punctuation">(</span>parsed<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">ParseError</span><span class="token punctuation">(</span>text<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> options<span class="token operator">?.</span>map <span class="token operator">?</span> options<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>parsed <span class="token keyword">as</span> MapperArgs<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token punctuation">(</span>parsed <span class="token keyword">as</span> To <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Plus some typing to make TypeScript happy:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ParseOptions<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To <span class="token operator">=</span> From<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> reviver<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> value<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">unknown</span><span class="token punctuation">;</span> map<span class="token operator">?</span><span class="token operator">:</span> Mapper<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To<span class="token operator">></span><span class="token punctuation">;</span> typeCheck<span class="token operator">?</span><span class="token operator">:</span> <span class="token operator">&lt;</span>To<span class="token operator">></span><span class="token punctuation">(</span>value<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token operator">=></span> value <span class="token keyword">is</span> To<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">StringifyOptions<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To <span class="token operator">=</span> From<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> map<span class="token operator">?</span><span class="token operator">:</span> Mapper<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To<span class="token operator">></span><span class="token punctuation">;</span> replacer<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>key<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> value<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">unknown</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ParseError</span> <span class="token keyword">extends</span> <span class="token class-name">Error</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span>text<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Cannot parse! </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>text<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Mapper<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To <span class="token operator">=</span> From<span class="token operator">></span></span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>value<span class="token operator">:</span> <span class="token builtin">unknown</span><span class="token punctuation">)</span> <span class="token operator">=></span> To<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>value<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>From<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> To<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>value<span class="token operator">:</span> From<span class="token punctuation">)</span> <span class="token operator">=></span> To<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>value<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>To<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> To<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>value<span class="token operator">:</span> To<span class="token punctuation">)</span> <span class="token operator">=></span> To<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>value<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>To <span class="token operator">|</span> From<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> To<span class="token punctuation">)</span> <span class="token operator">|</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>value<span class="token operator">:</span> To <span class="token operator">|</span> From<span class="token punctuation">)</span> <span class="token operator">=></span> To<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">MapperArgs<span class="token operator">&lt;</span>From<span class="token punctuation">,</span> To <span class="token operator">=</span> From<span class="token operator">></span></span> <span class="token operator">=</span> Partial<span class="token operator">&lt;</span>From<span class="token operator">></span> <span class="token operator">&amp;</span> From <span class="token operator">&amp;</span> Partial<span class="token operator">&lt;</span>To<span class="token operator">></span> <span class="token operator">&amp;</span> To<span class="token punctuation">;</span></code></pre></div> <p>It allows specifying additional mappers to map JSON back and forth, plus also <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse#the_reviver_parameter">reviver</a> and <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#the_replacer_parameter">replacer</a> methods to convert types automatically <a href="https://stackoverflow.com/a/14509447">to Date</a> and <a href="https://stackoverflow.com/a/69573383">to BigInt</a>. In our case, that’d not need an addtional serde type. We could also inline mappings to support payload versioning strategies.</p> <p><strong>Watch also more in the webinar:</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAAAAAAD6AF+hNEZAAABbUlEQVQoz4WS3W4TMRCF/dqgvgGXcM1LcIOQoKpIN222m+xmf/0zc2bGacljoN1NqFQhOFdj2eez54ydzhJV+asgWIv5gAqAl1/n+7tvnz9+uHn33gGXbb5W4LkEQEQgUXkDhlk+Pb+cz2dHDFUl5hQ8JYoxiZ0guvoPfpc4/fEvdADMzETkMHvVBNUQ98cdp1qOW429aLZsZVOEGPTaVVYxy2KndelCUIiYcOm1OP7s/WZT3Lb1NiuO1bG+78GX5w7DcPDU+Ya7Hc2dsYtJzYyYnzX3sSm7otrdteWPafOlejwQpTUKAPMFMVf1Lb5+Ejsxs/OjNzVeQlI1FZvaMozN5NNTUcYp4NKYdvsRgbNIGvYkM9TJEsYaD2NBWBY1VfHjVBfd0Pbd4/dpGg5FP/VhpoS2bAuBudcJvIqxUpYvACCEwMzRpxSJiIL3KZGqOL7O+R+6pL38k3VsTVP3Q3TM/ze/QTHjYVuUT+NvFbc119IGMYMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png" srcset="/static/5884d457fe2181ab0e490a1460ab913c/36ca5/2021-12-08-webinaresver.png 200w, /static/5884d457fe2181ab0e490a1460ab913c/a3397/2021-12-08-webinaresver.png 400w, /static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png 800w, /static/5884d457fe2181ab0e490a1460ab913c/8537d/2021-12-08-webinaresver.png 1200w, /static/5884d457fe2181ab0e490a1460ab913c/1a152/2021-12-08-webinaresver.png 1600w, /static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>And read in the versioning series:</strong></p> <ul> <li><a href="/en/simple_events_versioning_patterns/">Simple patterns for events schema versioning</a></li> <li><a href="/en/how_to_do_event_versioning/">How to (not) do the events versioning?</a></li> <li><a href="/en/how_to_map_event_type_by_convention/">Mapping event type by convention</a></li> <li><a href="/en/event_versioning_with_marten/">Event Versioning with Marten</a></li> <li><a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">Let’s take care of ourselves! Thoughts on compatibility</a></li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Don't let Event-Driven Architecture buzzwords fool you]]>https://event-driven.io/en/dont_let_event_driven_architecture_buzzwords_fool_you/https://event-driven.io/en/dont_let_event_driven_architecture_buzzwords_fool_you/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4335effe772a6a8306330ad60ff10809/a331c/2023-02-26-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAIAAAAmMtkJAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAC7ElEQVQoz0XS2VNSYRgG8O98bILIogjKoojC8RykUHYRFxQVQcWNEFzAlAQRSiGXNHA5ImOLpYUOZGo2eeN9003TX9BdN932hzSaTu/9b56Z53kBAEBYyncP+95ljw4yO7ubqYtPZ6cnJ+enJ8n19YUni7vp9Jfzs1z28Pj4OJfPW8wdPI6IRCJBCAGApGIaokFV87Nhz7ArOunbXUkQCzFiYTHq8Tx7PJ8/PNxIxMfcQ+MTfp/XZ1BhpnvYHUYQFo1cJRBpcRwC0IxWdWFV6pJic0mpVVbRrcX9XZZakRDcHJtO0coEWrkIgfAGA1AplGQ2Dnq6XVQatcdqy77JZFKr+TThatQ5G7ABUx0q4Ts0agOKtuob2jCxBa36jyXC2j77JJNZWEBnYGjNVtR9kVzOLE6PDdrtasWDjtZJp33E2oLLKjS6Zh2ubpJX3mIEATyuuIDKBgDQ6IwSDms/EswlEt5WU6deM9hicjQaXS0WeaWojFfEL5earRPNGgOCgGsMIcIs5NIZRbWYsphZKOZwMrOhtM+rq5WrMbRRKTfW4UYcI2YC6Ud+qVCoaxqtb7ABACAkARKZDADwDnm+/vi5E55rEAh9fcMztjYDi3mfx8YkfDWKikt5gWbDmtvBoFGleAeqar9NBhDSEbAdjmWXU46efgFPFBSUVktRK4tLsBkWfklkJrA+5w91W7x6pUleYVJqaxQmgCC3hcnE4o+R2IFvtF2n10qqrQpVb4V8VaV8EZ7z9zsvt+Kfn4YIT99SZ1NMKV9kF9pZbACvh77GWoP5968/b7c3NRqtrb5txOXLBGefRRYszsGHGmW0mDENQIxGipdz5xkUG4S9VAq8mwpxC4q+H70P9XYNOLuJndepFJE/O1tJbsdXk0Q0FnQ6d6amPiwvb1mMXgCqyVRDAY1Cgsi/D/Moyry4NDQyEAgEc9nc2vOttcTSdDD08tX+4d7e+Kh3P5X6dnV1GQkNQ8Alk5VkMhVe479k1b2x6oCGbwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4335effe772a6a8306330ad60ff10809/a331c/2023-02-26-cover.png" srcset="/static/4335effe772a6a8306330ad60ff10809/36ca5/2023-02-26-cover.png 200w, /static/4335effe772a6a8306330ad60ff10809/a3397/2023-02-26-cover.png 400w, /static/4335effe772a6a8306330ad60ff10809/a331c/2023-02-26-cover.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I see a lot of new terms like <a href="https://web.archive.org/web/20220816202019/https://developer.confluent.io/patterns/event/command-event/">Command-Event</a>, <a href="https://www.alexdebrie.com/posts/event-driven-vs-event-based/">Event-Based Compute</a>, etc., presented around Event-Driven Architecture. Let me clear that up because there are no such things.</strong></p> <p>The real split is Event-Driven Architecture vs Messaging.</p> <p><strong>Event-Driven Architecture (<em>EDA</em>) is about the logical composition of our workflow.</strong> We’re using events as the glue checkpoints of our workflow.</p> <p>Contrary to the traditional approach, we focus on verbs instead of nouns. So behaviours in our systems, interactions between them, reactions about new information and data in motion.</p> <p><strong>Events are excellent workflow facilitators.</strong> They represent facts on what has happened. We’re inverting the classical flow; instead of requesting everything, we’re notifying all interested parties. Then others can react and publish new information through events. <a href="/en/how_events_can_help_on_making_state_based_approach_efficient/">That helps to build decoupled and autonomous components</a>.</p> <p><strong>Events are messages, but they’re not the only message type. The second most important is <em>command</em>. They differ by intention.</strong> I wrote about it longer in <a href="/en/whats_the_difference_between_event_and_command/">What’s the difference between a command and an event?</a>. TLDR:</p> <p>A command is an intent to do something. It’s directed, and the handler may reject it.</p> <p>Events are facts; they can (and should) have multiple receivers. Yet, the event producers shouldn’t assume getting a response.</p> <p><strong>Understanding this split is essential for proper workflow composition.</strong> As much as we’d like our flow to be fire and forget, sometimes we need to have an explicit request to do something. Suppose we model workflow so that we always expect a particular event back as a follow-up. Is our event really broadcasted information or just an implicit command?</p> <p><strong>Events and commands do not differ by definition in terms of how they’re delivered, whether synchronous or asynchronous.</strong> The difference is in intention. The event doesn’t have to be asynchronous or use tooling like Kafka, Rabbit, SQS, etc. It’s about the composition of our flows, modelling conversation and structuring our architecture. It’s close to the <a href="https://wiki.c2.com/?AlanKaysDefinitionOfObjectOriented">original Alan Kay’s definition of Object Oriented Programming</a>.</p> <p><strong>Now, here we got to Messaging.</strong> It’s a set of patterns like Outbox, Inbox, Competing Consumers etc., focused on integration between our boundaries (modules, services etc.).</p> <p><strong>EDA and Messaging are different design perspectives.</strong> With EDA, you’re shaping your application’s conceptual/logical split, interactions, and how the flow looks.</p> <p>Knowing what we’d like to achieve, we can go one layer down and define how to do it. For instance, how to achieve guarantees like at-least-once delivery, strict ordering, and idempotent handlers. That’s Messaging; it tells how to handle communication efficiently and is part of the solution space. Yet it’s still agnostic to a specific technical solution. We should select the messaging tooling based on the design and whether it supports the Messaging patterns we have chosen to fulfil it.</p> <p><strong>The design flow should look like this:</strong></p> <ol> <li><strong>WHAT?</strong> Describe the business workflow and model it using Event-Driven Architecture. That includes communication flow between components, boundaries, etc. Great tools for that are <a href="https://www.eventstorming.com/">EventStorming</a>.</li> <li><strong>HOW?</strong> Design how to get guarantees and define the expected technical flow using Messaging patterns. They’re already established; no need to reinvent the wheel. Gregor Hohpe curated most of them in <a href="https://www.goodreads.com/book/show/85012.Enterprise_Integration_Patterns">Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions</a> 20 years ago.</li> <li><strong>WITH.</strong> Select messaging toolings like RabbitMQ, Kafka, SQS or others that match your requirements. As you see, this is the last phase and outcome of the previous steps.</li> </ol> <p><strong>Please do not mix them, or you’ll end up with a hangover!</strong></p> <p>If we flatten all that into a single perspective, we won’t be able to do a proper design. Without shaping the proper APIs based on intentions, we’ll end up with synchronous, direct communication but made implicitly in an asynchronous way. It is a nightmare to run on production as it’s hard to trace the processes and reason what and why went wrong. That usually ends up as distributed monolith. It’s a dark place with all cons of monolith and microservices but no pros.</p> <p><strong>Still not convinced? Let’s get back to asynchronous commands, then.</strong></p> <p>Some people claim that they’re not commands then but events. In my world, how we send commands between modules is part of technical design, so choosing our messaging patterns, tech stack and protocols.</p> <p>If we’re okay with in-proc handling, we can just use in-memory processing and call it a day.</p> <p>If we have a distributed system (e.g., service-oriented architecture, Kubernetes, multiple instances for scaling needs, etc.), we may need better delivery guarantees to ensure that our state is consistent. We may need <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox Pattern</a> or use a durable messaging queue.</p> <p><strong>Still, that choice doesn’t change the message semantics; the intention remains the same.</strong> We can use <a href="https://www.enterpriseintegrationpatterns.com/patterns/messaging/RequestReply.html">Request/Reply pattern</a> to get the result of the processing. All of the mature messaging toolings like <a href="https://wolverine.netlify.app/guide/messaging/message-bus.html#request-reply">Wolverine</a>, <a href="https://docs.nats.io/using-nats/developer/sending/request_reply">NATS JetStream</a> or <a href="https://docs.particular.net/nservicebus/messaging/reply-to-a-message">NServiceBus</a> provide that out of the box.</p> <p><strong>So why we’re getting terms like Command-Event, and Event-Based Compute?</strong> Because some companies were pushing the EDA buzzwords, Messaging didn’t sound sexy enough.</p> <p>They didn’t embrace semantics or teach their users existing patterns because the tooling didn’t support them out of the box. But that doesn’t change the fact that, eventually, their users need to understand that to succeed.</p> <p>Yet, if companies pushed all marketing into event-based terms ignoring existing patterns, how to tell now that they were wrong?</p> <p><strong>The easiest is to reinvent the wheel and introduce new gibberish terms.</strong></p> <p><strong>Curtain.</strong></p> <p>So, as architects, developers, we should remember to differentiate patterns from implementations. Be critical of what we read.</p> <p>That’s why I want to highlight how it’s important to break our design into multiple layers:</p> <ul> <li>logical,</li> <li>technical,</li> <li>implementation.</li> </ul> <p>That also plays well with tools like <a href="https://www.eventstorming.com/">EventStorming</a> and the <a href="https://c4model.com/">C4 Model</a>. All of them embrace that we have and should be able to zoom in and out.</p> <p>I know that it sounds like 4D Chess, but that’s our role. We need to train our abstract and critical thinking, as <a href="/en/the_magic_is_that_there_is_no_magic/">the magic is that there’s no magic</a>.</p> <p><strong>Don’t let the buzzwords and marketing fool you. Think for yourself, and question authorities.</strong></p> <p>If you liked this article, also check others where I’m trying to bust similar myths:</p> <ul> <li><a href="/en/event_streaming_is_not_event_sourcing/">Event Streaming is not Event Sourcing!</a></li> <li><a href="/en/cqrs_facts_and_myths_explained/">CQRS facts and myths explained</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Set up OpenTelemetry with Event Sourcing and Marten]]>https://event-driven.io/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/https://event-driven.io/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/28fce316f2b3deca4551769dd86170d0/c60e9/2023-02-19-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBf/EABUBAQEAAAAAAAAAAAAAAAAAAAAB/9oADAMBAAIQAxAAAAHZlsoxSX//xAAaEAABBQEAAAAAAAAAAAAAAAABAAIDEBEh/9oACAEBAAEFAj1ZZGh0r41//8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8BR//EABURAQEAAAAAAAAAAAAAAAAAAAAS/9oACAECAQE/AVP/xAAYEAADAQEAAAAAAAAAAAAAAAAAATESIP/aAAgBAQAGPwKlfGUz/8QAHBAAAwABBQAAAAAAAAAAAAAAAAERITFBYXGR/9oACAEBAAE/IU2OumWVM8eF1WVDeBrXKP/aAAwDAQACAAMAAAAQeM//xAAVEQEBAAAAAAAAAAAAAAAAAAAAAf/aAAgBAwEBPxBbf//EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAgEBPxCAm3//xAAcEAACAgIDAAAAAAAAAAAAAAABEQAxIVFBgdH/2gAIAQEAAT8QImB1QU97ibgcXfb2IImAxxAtNBnnM//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/28fce316f2b3deca4551769dd86170d0/c60e9/2023-02-19-cover.jpg" srcset="/static/28fce316f2b3deca4551769dd86170d0/37402/2023-02-19-cover.jpg 200w, /static/28fce316f2b3deca4551769dd86170d0/4cda9/2023-02-19-cover.jpg 400w, /static/28fce316f2b3deca4551769dd86170d0/c60e9/2023-02-19-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>If I had to select the Distributed Systems Song, I’d choose <a href="https://www.youtube.com/watch?v=TlBIa8z_Mts">Land of Confusion</a>.</strong> This is the world we live in. And these are the hands we’re given. Use them, and let’s start trying. To make it a place worth living in.</p> <p><strong>Through the years, we didn’t have a handful of options to make it a place worth living in.</strong> Yet, recently, we got <a href="https://opentelemetry.io/">Open Telemetry</a> which increases our likelihood to survive.</p> <h2 id="whats-open-telemetry" style="position:relative;"><a href="#whats-open-telemetry" aria-label="whats open telemetry permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What’s Open Telemetry?</h2> <p>Per its docs:</p> <p><em>OpenTelemetry is a collection of tools, APIs, and SDKs. Use it to instrument, generate, collect, and export telemetry data (metrics, logs, and traces) to help you analyze your software’s performance and behavior. OpenTelemetry is generally available across several languages and is suitable for use.</em></p> <p><strong>In other words, it’s standard with tooling to help us observe what’s happening in our systems.</strong></p> <p>If you want to learn more about its foundational concepts and how to apply them in .NET, Martin Thwaites’ talk <a href="https://www.youtube.com/watch?v=3JnMfJM9K0c">“Distributed Tracing in .NET 6 using OpenTelemetry”</a> is a decent place to start. You can also check the free <a href="https://info.honeycomb.io/observability-engineering-oreilly-book-2022">Observability Engineering</a> book that should be an excellent way to understand why observability is essential nowadays.</p> <p><strong>Let’s say that you’re building an E-Commerce flow and would like to understand both business and technical flow.</strong> You have a shopping cart module responsible for selecting products. Once you confirm the shopping cart, the order process is started and handled by a separate module.</p> <p>In distributed systems, many things can go wrong, e.g. failures in the business logic handling, services unavailability, <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">message delivery issues</a>, wrong idempotency handling, etc. It’d be great to be able to see what’s going on.</p> <p><strong>To understand that we need to understand two aspects correlation and causation.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/13a21fceb9c9c99eef712c5babafba52/a331c/2023-02-19-correlation.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7CAAAOwgEVKEqAAAAB8ElEQVQozy3Se2sTQRAA8Pv+30CoUAVbS6CCSJEqtqAgNKkhJk3StHe5177msc+7NP4ndwrDMsuyDPObyVx8AU4KA7p+tXk+eX169n7y5u3Zu/PJq5PTDx+vLiaX325/XEw+fLq6Pr+4LGrFvmfXoesyTUFRQNcZTqtNPpsvp/Pl3f3v+8V6sXqczVfz5fapaBYPu8VqN/21rARqjkABbcrAJsNRgtMUyPccDuh6TbFRtG90UZu81ruifS5lJUBjAI5gk8ZgOGaGYi1Rgms1t5okuFqC0Gw4kj8AoJJCKUHMQjthLNgOfU+2GypLY8kfXDq6eESXNPnx+ofjEX1v1T5/mJabqacW/UGjFwalQaEAOGZ5rStliwZKQYY7G/qAlVWFU7m3CDYCO2APNlI4Bgvt7r5Y36li2UrM1k/V015t8/ZxL0tJ0ti23NX5WpSPBkBR0uiQPZLTYDWQEk3T1FqpRmImwTeKxoZZ4cA2+KEfR9CD7aypy82s2s6sLsH2epyr0BY4ZcjJcDKDecKBYZAYFAVULdSSlKFW6KYVQpmyMZUACR44Dtol+NX2+frrzecvN7fffxqb/nlyfCHfaQrSOAFOgJfgyXcuvlA4/P+8V1w2qijbfS1qCeQS2oguoo3kEvuO/XgOeRpfxxj35C9L+U6Uob9oWQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/13a21fceb9c9c99eef712c5babafba52/a331c/2023-02-19-correlation.png" srcset="/static/13a21fceb9c9c99eef712c5babafba52/36ca5/2023-02-19-correlation.png 200w, /static/13a21fceb9c9c99eef712c5babafba52/a3397/2023-02-19-correlation.png 400w, /static/13a21fceb9c9c99eef712c5babafba52/a331c/2023-02-19-correlation.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Correlation gives us information about everything that happened during a single process/request.</strong> The most straightforward way is to generate a unique index for a request/process. Having that, we can pass it through all the tooling we use and put it in logs, etc.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/fc7edca88484965b7ec9a2960e05eac7/a331c/2023-02-19-causation.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7CAAAOwgEVKEqAAAAB5UlEQVQoz0VS2U7cQBD0//9BFEU5UEIgASlSTgUpyQMPBEwWWGDZgL22x3N19/QcthfeolkeIpVKLY26uqp7Chcm4KQggBuvl/WzF6+2Xm+/3Np+s/P+ydPnex8+7bzb//Hr8O3u3scv37d396tWkh+JB+Ch0BAURuuSoTRfVOVsfnx6/rs8L2eXZ5fLkz/z2cXib9WdXS5n85vj04umB03RYgCXCkMRssyIG86FHzFMyKOC0FsvDDcSO+1645RlQyFPgmApFuAS8GA3/FhkcGb0EwAoKZQUSJRFN+oZbsjNFCYX1y7z5MLowoRh7eI9xQerW5I31VXZXJ94EMADckJOFAbI82PRa256rAWsJHbG94a9WbGunLpllJZHIDbE4CKFB++sWJ7czY9sc5UzV51d1v3iTixr2UjqjTf9StQLsbrRslFKavBAHoi1ASXuZC/atkUAQ7Gw7jHGgDzSxjaFtQv3FNaGopIt6Xq1KJtFSfJWW4SQEyFPebLFaClpStYNhpLBZCgajAqCzpyMJSF120ltSWPU4DXmUxlKRa257vTnrwffDn4eHpWbhQ/oR8oWpvwf/ETxPnvxU95rmNCPlmJuriS2valWXSNUrwFczOCYT+giukiciBO6iBz/v1K0FP4BvK1SFXC8JsMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/fc7edca88484965b7ec9a2960e05eac7/a331c/2023-02-19-causation.png" srcset="/static/fc7edca88484965b7ec9a2960e05eac7/36ca5/2023-02-19-causation.png 200w, /static/fc7edca88484965b7ec9a2960e05eac7/a3397/2023-02-19-causation.png 400w, /static/fc7edca88484965b7ec9a2960e05eac7/a331c/2023-02-19-causation.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Causation gives us information about the previous step (parent) that triggered our activity.</strong> For instance, we can see that loading and storing entities were initiated during command handling.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/29a3b9da1b93bdf75340b4d2b52d6232/a331c/2023-02-19-causation-and-correlation.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7CAAAOwgEVKEqAAAACAUlEQVQozyWRXU9TQRCGz///CSZESYwExRAISdWKGkwIFKoUsG0Kp6e7Z3dnd2a/95y2l6aQzMVczfvO81QhbSgUoESh/zev997uHxwe7b8/+HD4+c3eu5OzweGn4/Nfv4+OT88Gw49HJ02rXexd6Ch0laaobSLfGVceZs+j8f1oPLm+nfyZTO8e5qPx/XgyXdTs7mH+9352dXPHJGqX0Sb0pTKuGFcU7k7Y2Lu0ptCDTUzikqlla5bcLJayZsAlaUrGZfRFUzQ2V8YmrkhhENoKbRUGLlFohy67tEGyoBSAss4LHaTx5DsKPfkOXamk8Tb2oWx92dhQjEsU16FsXd6i5k7O68fRanoVSVBcawpSkzIkAI0rVc2Bg1+2uJJWYAKbI7KgGw+1t8ruQiL6SL64vE2RxOK2md1A8yiAqumCPTUwr8VTo+oWVwLFasHrqWRPWjIFoCmSi2QDaATxrGTbcq4VtMpWEmOr7OvDQMG4pF2WGMCm3Q6t100zu2XzsVO1JqddpygJ2EGpyGUKHfoOfUHfubjeAY89V8QkcfBgrFC6FVICNYK4RMBILhuXq8bEi8ubwdfvg2/nl9fj4fnFl+HP4Y+LmkmfXwgZL02QGBQGisXntY092rxTxSnVHJ6ZWgkDNknjV9IwIGOTz9tQtvFlUtm8KNi4vPFl+9ruP4oIS/Uofo/9AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/29a3b9da1b93bdf75340b4d2b52d6232/a331c/2023-02-19-causation-and-correlation.png" srcset="/static/29a3b9da1b93bdf75340b4d2b52d6232/36ca5/2023-02-19-causation-and-correlation.png 200w, /static/29a3b9da1b93bdf75340b4d2b52d6232/a3397/2023-02-19-causation-and-correlation.png 400w, /static/29a3b9da1b93bdf75340b4d2b52d6232/a331c/2023-02-19-causation-and-correlation.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>That’s what Open Telemetry gives out of the box, standardising and handling it for us.</strong> In .NET, it’s even neater, as instrumentation is <a href="https://learn.microsoft.com/en-us/dotnet/core/diagnostics/distributed-tracing-instrumentation-walkthroughs">built-in into the diagnostics mechanism</a>.</p> <p>In OpenTelemetry, we have traces representing the whole process and spans representing nested processing. Using them, we can get a tree representing the flow of our requests/processes.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 666px; height: auto" > <a class="gatsby-resp-image-link" href="/static/365a227d23d0843cd96e228f3f968cf9/029bd/2023-02-19-traces.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 44.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABwUlEQVQoz2O4cetWXUNbU2N3Q11HY31HU11HY117I4jsaKxtb6prb67vbK5rb67rgKH2tsbu0pKa5avXMLx/+6F2RrNdsbtVoYt5votBnptRgbtJgYdxgYdpkadJgYd2nqtmgZtmvqtmnotmvptWgZtKtkNAdeLVy9cY/v//v+jiTZfFG5wWrondsL5j65y8+f2587rz5vekTmstWDph466VuxfN3LFyzrb1i3asXrh32byNW5ecfn31////DP///zv98FfP9uedWx4ffvh/15b9efmtB/ac37/n3KZ1h0+dvvnnwcdLkw7fWHDm+rIzN+YdPNO15d3BW//////z5w/Dv///////9+7tq6dPH//////U2dO9kyZ9+PTh05eP7z68+/v/75cfn5cua1q6tGbd5oVr956cu37XpTsPQHr+/QM5+++//2Aj/v+BUGAJKA0SffP7nMmnfWL/X/aCxf78hwEGuGqIhr9///7+8+cfFPwFG/nu21mX9we1/r+Y8w9kwR+YXTDNOABY0Z+3P06ZvNql8PVq9o+Xu/6/Xvv/91uIhURo/vvl34Oad5crn58te3c++9/tlP/f74Al/wIA7LWh8zl40FsAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/365a227d23d0843cd96e228f3f968cf9/029bd/2023-02-19-traces.png" srcset="/static/365a227d23d0843cd96e228f3f968cf9/36ca5/2023-02-19-traces.png 200w, /static/365a227d23d0843cd96e228f3f968cf9/a3397/2023-02-19-traces.png 400w, /static/365a227d23d0843cd96e228f3f968cf9/029bd/2023-02-19-traces.png 666w" sizes="(max-width: 666px) 100vw, 666px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Event Sourcing gives us even more options; we can combine tracing with business facts we store.</strong> Typically, we put them into event metadata. By that, we can see correlate technical traces with business information. That’s super powerful if we try to diagnose what went wrong. Observe trends, etc.</p> <h2 id="how-to-do-it-in-practice" style="position:relative;"><a href="#how-to-do-it-in-practice" aria-label="how to do it in practice permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How to do it in practice?</h2> <p><strong>Let’s say that we’re using <a href="https://martendb.io">Marten</a>, and we have the following generic way of handling our commands:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DocumentSessionExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token punctuation">{</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">StartStream</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> documentSession<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">token</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">GetAndUpdate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> version<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span></span> handle<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token operator">=></span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">WriteToAggregate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> version<span class="token punctuation">,</span> stream <span class="token operator">=></span> stream<span class="token punctuation">.</span><span class="token function">AppendOne</span><span class="token punctuation">(</span><span class="token function">handle</span><span class="token punctuation">(</span>stream<span class="token punctuation">.</span>Aggregate<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We either start the stream if we expect the event to be the first in the stream (e.g. when we’re opening the shopping cart) or we’re <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">getting the state from events</a> and appending new fact as the result of the command handling logic (<a href="https://martendb.io/scenarios/command_handler_workflow.html#writetoaggregate">WriteToAggregate</a> will do that for us). For examples of handling business logic, check <a href="/en/slim_your_entities_with_event_sourcing/">this article</a>.</p> <p><strong>Marten allows <a href="https://martendb.io/documents/metadata.html#correlation-id-causation-id-and-headers">defining metadata on the DocumentSession level</a>. We allow predefined fields like CorrelationId, CausationId, and custom ones.</strong> Whatever we store in Marten in that session lifetime (so events, documents, projections, etc.) will get that metadata. We’ll use that feature for OpenTelemetry integration.</p> <p>To avoid mixing storage handling with telemetry processing to make that more understandable, let’s add decorators for our methods that will fill the necessary telemetry data.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DocumentSessionExtensionsWithOpenTelemetry</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token operator">=></span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">WithTelemetry</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> token <span class="token operator">=></span> DocumentSessionExtensions<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>documentSession<span class="token punctuation">,</span> id<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> token<span class="token punctuation">)</span><span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">GetAndUpdate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> version<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span></span> handle<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token operator">=></span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">WithTelemetry</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> token <span class="token operator">=></span> DocumentSessionExtensions<span class="token punctuation">.</span><span class="token function">GetAndUpdate</span><span class="token punctuation">(</span>documentSession<span class="token punctuation">,</span> id<span class="token punctuation">,</span> version<span class="token punctuation">,</span> handle<span class="token punctuation">,</span> token<span class="token punctuation">)</span><span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">WithTelemetry</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">></span></span> run<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerMemberName</span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span></span> memberName <span class="token operator">=</span> <span class="token string">""</span> <span class="token punctuation">)</span> <span class="token operator">=></span> ActivityScope<span class="token punctuation">.</span>Instance<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp"><span class="token keyword">nameof</span><span class="token punctuation">(</span>DocumentSessionExtensions<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">memberName</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> <span class="token punctuation">(</span>activity<span class="token punctuation">,</span> token<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> documentSession<span class="token punctuation">.</span><span class="token function">PropagateTelemetry</span><span class="token punctuation">(</span>activity<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">run</span><span class="token punctuation">(</span>token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StartActivityOptions</span> <span class="token punctuation">{</span> Tags <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> TelemetryTags<span class="token punctuation">.</span>Logic<span class="token punctuation">.</span>Stream<span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">T</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re starting a new activity with a name equal to the method we’re doing, so either <em>DocumentSessionExtensions/Add</em> or <em>DocumentSessionExtensions/GetOrUpdate</em>. The activity, by default, will be created as the root one or nested if there’s already a parent activity. We’re also passing additional tags with information about our Stream/Entity type (you can add more depending on your needs).</p> <p>Once the activity is started, we’re propagating telemetry data to the document session and calling the decorated method.</p> <p><strong>The propagation code looks as follows:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">OpenTelemetryExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">PropagateTelemetry</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Activity<span class="token punctuation">?</span></span> activity<span class="token punctuation">,</span> <span class="token class-name">ILogger<span class="token punctuation">?</span></span> logger <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> propagationContext <span class="token operator">=</span> activity<span class="token punctuation">.</span><span class="token function">Propagate</span><span class="token punctuation">(</span> documentSession<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>session<span class="token punctuation">,</span> key<span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> session<span class="token punctuation">.</span><span class="token function">InjectTelemetryIntoDocumentSession</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">,</span> logger<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>propagationContext<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> documentSession<span class="token punctuation">.</span>CorrelationId <span class="token operator">=</span> propagationContext<span class="token punctuation">.</span>Value<span class="token punctuation">.</span>ActivityContext<span class="token punctuation">.</span>TraceId<span class="token punctuation">.</span><span class="token function">ToHexString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> documentSession<span class="token punctuation">.</span>CausationId <span class="token operator">=</span> propagationContext<span class="token punctuation">.</span>Value<span class="token punctuation">.</span>ActivityContext<span class="token punctuation">.</span>SpanId<span class="token punctuation">.</span><span class="token function">ToHexString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">InjectTelemetryIntoDocumentSession</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> key<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token class-name">ILogger<span class="token punctuation">?</span></span> logger <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> session<span class="token punctuation">.</span><span class="token function">SetHeader</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span> logger<span class="token punctuation">?.</span><span class="token function">LogError</span><span class="token punctuation">(</span>ex<span class="token punctuation">,</span> <span class="token string">"Failed to inject trace context"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re using the built-in activity’s _Propagate method that takes the specific object, and for each of the <a href="https://www.w3.org/TR/trace-context/#trace-context-http-headers-format">telemetry headers</a> calls the provided method.</p> <p>We’re using Marten’s <em>SetHeader</em> that stores custom metadata.</p> <p>Then finally, we’re also storing trace and span information in <em>CorrelationId</em> and <em>CausationId</em>.</p> <p>Ah, and <em>ActivityScope.Instance.Run</em> is also my custom wrapper. Here’s the code for it:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ActivityScope</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token class-name">ActivityScope</span> Instance <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ActivityScope</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> GeneralPrefix <span class="token operator">=</span> <span class="token string">"event_driven_io"</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">Activity<span class="token punctuation">?</span></span> <span class="token function">Start</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name">StartActivityOptions</span> options<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token return-type class-name">options<span class="token punctuation">.</span>Parent<span class="token punctuation">.</span>HasValue <span class="token punctuation">?</span></span> ActivitySourceProvider<span class="token punctuation">.</span>Instance <span class="token punctuation">.</span><span class="token function">CreateActivity</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">GeneralPrefix</span><span class="token punctuation">}</span></span><span class="token string">.</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">name</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> options<span class="token punctuation">.</span>Kind<span class="token punctuation">,</span> <span class="token named-parameter punctuation">parentContext</span><span class="token punctuation">:</span> options<span class="token punctuation">.</span>Parent<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> <span class="token named-parameter punctuation">idFormat</span><span class="token punctuation">:</span> ActivityIdFormat<span class="token punctuation">.</span>W3C<span class="token punctuation">,</span> <span class="token named-parameter punctuation">tags</span><span class="token punctuation">:</span> options<span class="token punctuation">.</span>Tags <span class="token punctuation">)</span><span class="token punctuation">?.</span><span class="token function">Start</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> ActivitySourceProvider<span class="token punctuation">.</span>Instance <span class="token punctuation">.</span><span class="token function">CreateActivity</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">GeneralPrefix</span><span class="token punctuation">}</span></span><span class="token string">.</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">name</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> options<span class="token punctuation">.</span>Kind<span class="token punctuation">,</span> <span class="token named-parameter punctuation">parentId</span><span class="token punctuation">:</span> options<span class="token punctuation">.</span>ParentId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">idFormat</span><span class="token punctuation">:</span> ActivityIdFormat<span class="token punctuation">.</span>W3C<span class="token punctuation">,</span> <span class="token named-parameter punctuation">tags</span><span class="token punctuation">:</span> options<span class="token punctuation">.</span>Tags <span class="token punctuation">)</span><span class="token punctuation">?.</span><span class="token function">Start</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Run</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>Activity<span class="token punctuation">?</span><span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">></span></span> run<span class="token punctuation">,</span> <span class="token class-name">StartActivityOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> activity <span class="token operator">=</span> <span class="token function">Start</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> options<span class="token punctuation">)</span> <span class="token operator">??</span> Activity<span class="token punctuation">.</span>Current<span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">run</span><span class="token punctuation">(</span>activity<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> activity<span class="token punctuation">?.</span><span class="token function">SetStatus</span><span class="token punctuation">(</span>ActivityStatusCode<span class="token punctuation">.</span>Ok<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">{</span> activity<span class="token punctuation">?.</span><span class="token function">SetStatus</span><span class="token punctuation">(</span>ActivityStatusCode<span class="token punctuation">.</span>Error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>TResult<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Run</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TResult<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>Activity<span class="token punctuation">?</span><span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">&lt;</span>TResult<span class="token punctuation">></span><span class="token punctuation">></span></span> run<span class="token punctuation">,</span> <span class="token class-name">StartActivityOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> activity <span class="token operator">=</span> <span class="token function">Start</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> options<span class="token punctuation">)</span> <span class="token operator">??</span> Activity<span class="token punctuation">.</span>Current<span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">run</span><span class="token punctuation">(</span>activity<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> activity<span class="token punctuation">?.</span><span class="token function">SetStatus</span><span class="token punctuation">(</span>ActivityStatusCode<span class="token punctuation">.</span>Ok<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">{</span> activity<span class="token punctuation">?.</span><span class="token function">SetStatus</span><span class="token punctuation">(</span>ActivityStatusCode<span class="token punctuation">.</span>Error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">StartActivityOptions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Dictionary<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">?</span><span class="token punctuation">></span></span> Tags <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> ParentId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ActivityContext<span class="token punctuation">?</span></span> Parent <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token class-name">ActivityKind</span> Kind <span class="token operator">=</span> ActivityKind<span class="token punctuation">.</span>Internal<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Nothing spectacular is going on there. It’s just setting up the activity, as described above, with specified options and marking its processing status. Eventually, you should enrich stored observability data. I’m showing here the bare minimum.</p> <h2 id="how-to-propagate-telemetry-in-event-driven-processing" style="position:relative;"><a href="#how-to-propagate-telemetry-in-event-driven-processing" aria-label="how to propagate telemetry in event driven processing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How to propagate telemetry in Event-Driven processing?</h2> <p>We now know how to instrument our command handling with telemetry information, but that’s only half the story.</p> <p>For now, we have all we need to see how the shopping cart confirmation went, but we still don’t know how to express that order initialisation was caused by it.</p> <p><strong>To do that, we need to propagate context.</strong> In another article, <a href="/en/integrating_Marten/">I explained how to subscribe for notifications about new events</a>. We can use them to push events forward to the messaging system or call event handlers.</p> <p>To get a full telemetry setup, we need to assign the parent context from event metadata. Then we’ll know that order initiation is part of the same process as shopping cart confirmation (as an event handler that initiated the order was triggered by the shopping cart confirmed event).</p> <p>Let’s see how our improved consumer with telemetry data could look like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MartenEventPublisher</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMartenEventsConsumer</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IServiceProvider</span> serviceProvider<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IActivityScope</span> activityScope<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ILogger<span class="token punctuation">&lt;</span>MartenEventPublisher<span class="token punctuation">></span></span> logger<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MartenEventPublisher</span><span class="token punctuation">(</span> <span class="token class-name">IServiceProvider</span> serviceProvider<span class="token punctuation">,</span> <span class="token class-name">IActivityScope</span> activityScope<span class="token punctuation">,</span> <span class="token class-name">ILogger<span class="token punctuation">&lt;</span>MartenEventPublisher<span class="token punctuation">></span></span> logger <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>serviceProvider <span class="token operator">=</span> serviceProvider<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>activityScope <span class="token operator">=</span> activityScope<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>logger <span class="token operator">=</span> logger<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">ConsumeAsync</span><span class="token punctuation">(</span> <span class="token class-name">IDocumentOperations</span> documentOperations<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streamActions<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellationToken <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> streamActions<span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>streamAction <span class="token operator">=></span> streamAction<span class="token punctuation">.</span>Events<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> parentContext <span class="token operator">=</span> TelemetryPropagator<span class="token punctuation">.</span><span class="token function">Extract</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Headers<span class="token punctuation">,</span> ExtractTraceContextFromEventMetadata<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> activityScope<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp"><span class="token keyword">nameof</span><span class="token punctuation">(</span>MartenEventPublisher<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp"><span class="token keyword">nameof</span><span class="token punctuation">(</span>ConsumeAsync<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> scope <span class="token operator">=</span> serviceProvider<span class="token punctuation">.</span><span class="token function">CreateScope</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> eventBus <span class="token operator">=</span> scope<span class="token punctuation">.</span>ServiceProvider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEventBus<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> eventMetadata <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventMetadata</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Id<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">ulong</span><span class="token punctuation">)</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Version<span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">ulong</span><span class="token punctuation">)</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Sequence<span class="token punctuation">,</span> parentContext <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> eventBus<span class="token punctuation">.</span><span class="token function">Publish</span><span class="token punctuation">(</span>EventEnvelopeFactory<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">,</span> eventMetadata<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StartActivityOptions</span> <span class="token punctuation">{</span> Tags <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> TelemetryTags<span class="token punctuation">.</span>EventHandling<span class="token punctuation">.</span>Event<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> Parent <span class="token operator">=</span> parentContext<span class="token punctuation">.</span>ActivityContext <span class="token punctuation">}</span><span class="token punctuation">,</span> cancellationToken <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">IEnumerable<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> <span class="token function">ExtractTraceContextFromEventMetadata</span><span class="token punctuation">(</span><span class="token class-name">Dictionary<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span><span class="token punctuation">?</span></span> headers<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> key<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>headers<span class="token operator">!</span><span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token keyword">return</span> Enumerable<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Empty</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> stringValue <span class="token operator">=</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> stringValue <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> stringValue <span class="token punctuation">}</span> <span class="token punctuation">:</span> Enumerable<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Empty</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span> logger<span class="token punctuation">.</span><span class="token function">LogError</span><span class="token punctuation">(</span><span class="token string">"Failed to extract trace context: {ex}"</span><span class="token punctuation">,</span> ex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> Enumerable<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Empty</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re using here the built-in OpenTelemetry propagator to extract the parent context and pass it to already known <em>ActivityScope.Run</em> method. We’re using a similar pattern, but this time doing the other way round: taking metadata from events and starting activity with them.</p> <p><strong>We’re also forwarding metadata into the event envelope and calling the in-memory event bus that will call registered event handlers.</strong></p> <p>The event bus for each of the handlers will start a dedicated activity, similarly as we do for command handling:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventBus</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventBus</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IServiceProvider</span> serviceProvider<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IActivityScope</span> activityScope<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">EventBus</span><span class="token punctuation">(</span> <span class="token class-name">IServiceProvider</span> serviceProvider<span class="token punctuation">,</span> <span class="token class-name">IActivityScope</span> activityScope<span class="token punctuation">,</span> <span class="token class-name">AsyncPolicy</span> retryPolicy <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>serviceProvider <span class="token operator">=</span> serviceProvider<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>activityScope <span class="token operator">=</span> activityScope<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>retryPolicy <span class="token operator">=</span> retryPolicy<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">Publish</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span> eventEnvelope<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TEvent</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token punctuation">{</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> scope <span class="token operator">=</span> serviceProvider<span class="token punctuation">.</span><span class="token function">CreateScope</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> eventName <span class="token operator">=</span> eventEnvelope<span class="token punctuation">.</span>Data<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> activityOptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StartActivityOptions</span> <span class="token punctuation">{</span> Tags <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> TelemetryTags<span class="token punctuation">.</span>EventHandling<span class="token punctuation">.</span>Event<span class="token punctuation">,</span> eventName <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> eventHandlers <span class="token operator">=</span> scope<span class="token punctuation">.</span>ServiceProvider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetServices</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEventHandler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> eventHandler <span class="token keyword">in</span> eventHandlers<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> activityName <span class="token operator">=</span> <span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">eventHandler<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name</span><span class="token punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">eventName</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">;</span> <span class="token keyword">await</span> activityScope<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span> activityName<span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> token<span class="token punctuation">)</span> <span class="token operator">=></span> eventHandler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>eventEnvelope<span class="token punctuation">.</span>Data<span class="token punctuation">,</span> token<span class="token punctuation">)</span><span class="token punctuation">,</span> activityOptions<span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>That gives us an entire flow and allows us to build pretty complex and observable workflows.</strong></p> <h2 id="further-steps" style="position:relative;"><a href="#further-steps" aria-label="further steps permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Further steps</h2> <p>OpenTelemetry is powerful, but it takes time to get familiar with it and understand the necessary steps. It’s not complicated, but it has a lot of options and tools to plug together. I hope that this will give you enough to start your implementation.</p> <p>We want to enrich Marten’s support of OpenTelemetry, so less stuff is needed to orchestrate that. You can already use <a href="https://jeremydmiller.com/2023/01/02/wolverine-delivers-the-instrumentation-you-want-and-need/">Wolverine</a> that solves instrumentation for you.</p> <p>Understanding the technical aspect of observability is foundational. If you don’t enable it, you won’t have it. <strong>Still, it’s just a first step. You need to put observability as a core part of your design.</strong> It needs to be thought, planned and included in your development process. Only that will give you a chance to not only set it up but get real observability.</p> <p>For the whole setup, check my sample repository: <a href="https://github.com/oskardudycz/EventSourcing.NetCore">Event Sourcing in .NET</a>. It also has a similar setup made for EventStoreDB.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Event-driven projections in Marten explained]]>https://event-driven.io/en/projections_in_marten_explained/https://event-driven.io/en/projections_in_marten_explained/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d4a7b30f8a33dda960a692bfe222f01c/c60e9/2023-02-12-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMFAf/EABQBAQAAAAAAAAAAAAAAAAAAAAL/2gAMAwEAAhADEAAAAX04N8Jhgz//xAAaEAACAgMAAAAAAAAAAAAAAAACAwABBDEy/9oACAEBAAEFAhYbHjuYbCt581P/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAZEAACAwEAAAAAAAAAAAAAAAABEQACIRD/2gAIAQEABj8CqDilsXETM5//xAAcEAACAgIDAAAAAAAAAAAAAAAAEQExEFEhQaH/2gAIAQEAAT8hstevReK694i7ESx2ZTsof//aAAwDAQACAAMAAAAQq8//xAAXEQADAQAAAAAAAAAAAAAAAAABECFR/9oACAEDAQE/EBZq/8QAGBEAAgMAAAAAAAAAAAAAAAAAARARIVH/2gAIAQIBAT8Qgi8X/8QAHBABAAICAwEAAAAAAAAAAAAAAQARITFBUYGh/9oACAEBAAE/ED+XYhTjbvqNXD2X1Dm4hnIXtwwZ7cYEvW22jPk//9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/d4a7b30f8a33dda960a692bfe222f01c/c60e9/2023-02-12-cover.jpg" srcset="/static/d4a7b30f8a33dda960a692bfe222f01c/37402/2023-02-12-cover.jpg 200w, /static/d4a7b30f8a33dda960a692bfe222f01c/4cda9/2023-02-12-cover.jpg 400w, /static/d4a7b30f8a33dda960a692bfe222f01c/c60e9/2023-02-12-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Projections are a neverending story. One does not simply write <a href="">a single guide</a>. One needs to write more examples of more advanced cases. That’s precisely what one here intends to do!</p> <p>Let’s say that we’re working on the helpdesk system. We’re gathering information about incidents provided by the customer.</p> <p>The flow is probably well-known to you, but let’s summarise it:</p> <ul> <li>Incident is logged. It can be received via various channels (application, email, phone, etc.).</li> <li>After that, it should be categorised and prioritised by the support agent.</li> <li>The discussion between the agent and customer is happening.</li> <li>The agent has to answer all customer questions before resolving the incident.</li> <li>Resolution needs to be acknowledged by the customer.</li> <li>After customer acknowledgement incident can be closed.</li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4bdc859ed3be73bd95980ea1b709c4f2/c60e9/2023-02-12-incidents.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 249.00000000000003%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAyABQDASIAAhEBAxEB/8QAGAABAQEBAQAAAAAAAAAAAAAAAAIBAwX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgD/2gAMAwEAAhADEAAAAffmuMvcUTN8peooyayWxRg20bf/xAAeEAABBAIDAQAAAAAAAAAAAAACAAERMRAyEiBBQ//aAAgBAQABBQLwZjH0T0OuKdFQRxT0OvT/xAAWEQEBAQAAAAAAAAAAAAAAAAAgAUH/2gAIAQMBAT8Bgwf/xAAWEQEBAQAAAAAAAAAAAAAAAAAgAUH/2gAIAQIBAT8Bo0f/xAAcEAACAgIDAAAAAAAAAAAAAAABEAAxESEgQVH/2gAIAQEABj8Cm7ddP1gAaRgw6xx//8QAHhAAAgICAgMAAAAAAAAAAAAAAREhMQAQIFFhcYH/2gAIAQEAAT8hLZXhIjsAJt5avlReVT7aIYINYzUm+tJMUFlkY1E4cVhsD8OP/9oADAMBAAIAAwAAABA4BbxIDwD/xAAYEQEAAwEAAAAAAAAAAAAAAAABEBEhIP/aAAgBAwEBPxDQkGldP//EABgRAQEAAwAAAAAAAAAAAAAAAAERECAh/9oACAECAQE/EFEcPG7P/8QAHhABAAEEAwEBAAAAAAAAAAAAAREAECFBMVFxIGH/2gAIAQEAAT8Q1HBie6YTwyxeBy+3HltfneP2seu47sRiUQlGR8p0ocUETSJTRQAJHhd2YlhF6oBoE5a+f//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4bdc859ed3be73bd95980ea1b709c4f2/c60e9/2023-02-12-incidents.jpg" srcset="/static/4bdc859ed3be73bd95980ea1b709c4f2/37402/2023-02-12-incidents.jpg 200w, /static/4bdc859ed3be73bd95980ea1b709c4f2/4cda9/2023-02-12-incidents.jpg 400w, /static/4bdc859ed3be73bd95980ea1b709c4f2/c60e9/2023-02-12-incidents.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In code, our events could look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentLogged</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> CustomerId<span class="token punctuation">,</span> <span class="token class-name">Contact</span> Contact<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Description<span class="token punctuation">,</span> <span class="token class-name">Guid</span> LoggedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> LoggedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentCategorised</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">IncidentCategory</span> Category<span class="token punctuation">,</span> <span class="token class-name">Guid</span> CategorisedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> CategorisedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentPrioritised</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">IncidentPriority</span> Priority<span class="token punctuation">,</span> <span class="token class-name">Guid</span> PrioritisedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> PrioritisedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AgentAssignedToIncident</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> AgentId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> AssignedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AgentRespondedToIncident</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">IncidentResponse<span class="token punctuation">.</span>FromAgent</span> Response<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> RespondedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CustomerRespondedToIncident</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">IncidentResponse<span class="token punctuation">.</span>FromCustomer</span> Response<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> RespondedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentResolved</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">ResolutionType</span> Resolution<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ResolvedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ResolvedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ResolutionAcknowledgedByCustomer</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> AcknowledgedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> AcknowledgedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentClosed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClosedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ClosedAt <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Having them, we can build various projections. The most obvious are incident details and short info.</strong></p> <p>The first one will show all the information about the details and is used during the resolution process. The second shows basic information for a list view.</p> <p><strong>They are built from the same set of events from incident streams.</strong> A single stream will end up as the single record in the read model.</p> <p><strong>How to model them in <a href="https://martendb.io/">Marten</a>?</strong> We’ve got you covered! For that, use <a href="https://martendb.io/events/projections/aggregate-projections.html#aggregate-by-stream">Single Stream aggregations</a>. They’re dedicated to such 1:1 handling.</p> <p>Marten will take the stream id, try to match it with the read model id and perform an upsert. You just need to provide the logic for applying the event on the read model item.</p> <p>Short info projection can look like that <em>(Note: read-only syntax is not required, you can also use regular mutations</em>).</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentShortInfo</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> CustomerId<span class="token punctuation">,</span> <span class="token class-name">IncidentStatus</span> Status<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> NotesCount<span class="token punctuation">,</span> <span class="token class-name">IncidentCategory<span class="token punctuation">?</span></span> Category <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token class-name">IncidentPriority<span class="token punctuation">?</span></span> Priority <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IncidentShortInfoProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">SingleStreamProjection<span class="token punctuation">&lt;</span>IncidentShortInfo<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IncidentShortInfo</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">IncidentLogged</span> logged<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>logged<span class="token punctuation">.</span>IncidentId<span class="token punctuation">,</span> logged<span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> IncidentStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentShortInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentCategorised</span> categorised<span class="token punctuation">,</span> <span class="token class-name">IncidentShortInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Category <span class="token operator">=</span> categorised<span class="token punctuation">.</span>Category <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentShortInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentPrioritised</span> prioritised<span class="token punctuation">,</span> <span class="token class-name">IncidentShortInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Priority <span class="token operator">=</span> prioritised<span class="token punctuation">.</span>Priority <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentShortInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">AgentRespondedToIncident</span> agentResponded<span class="token punctuation">,</span> <span class="token class-name">IncidentShortInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> NotesCount <span class="token operator">=</span> current<span class="token punctuation">.</span>NotesCount <span class="token operator">+</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentShortInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">CustomerRespondedToIncident</span> customerResponded<span class="token punctuation">,</span> <span class="token class-name">IncidentShortInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> NotesCount <span class="token operator">=</span> current<span class="token punctuation">.</span>NotesCount <span class="token operator">+</span> <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentShortInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentResolved</span> resolved<span class="token punctuation">,</span> <span class="token class-name">IncidentShortInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> IncidentStatus<span class="token punctuation">.</span>Resolved <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentShortInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ResolutionAcknowledgedByCustomer</span> acknowledged<span class="token punctuation">,</span> <span class="token class-name">IncidentShortInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> IncidentStatus<span class="token punctuation">.</span>ResolutionAcknowledgedByCustomer <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentShortInfo</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentClosed</span> closed<span class="token punctuation">,</span> <span class="token class-name">IncidentShortInfo</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> IncidentStatus<span class="token punctuation">.</span>Closed <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Details projection can look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentDetails</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> CustomerId<span class="token punctuation">,</span> <span class="token class-name">IncidentStatus</span> Status<span class="token punctuation">,</span> <span class="token class-name">IncidentNote<span class="token punctuation">[</span><span class="token punctuation">]</span></span> Notes<span class="token punctuation">,</span> <span class="token class-name">IncidentCategory<span class="token punctuation">?</span></span> Category <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token class-name">IncidentPriority<span class="token punctuation">?</span></span> Priority <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> AgentId <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Version <span class="token operator">=</span> <span class="token number">1</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentNote</span><span class="token punctuation">(</span> <span class="token class-name">IncidentNoteType</span> Type<span class="token punctuation">,</span> <span class="token class-name">Guid</span> From<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Content<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> VisibleToCustomer <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">IncidentNoteType</span> <span class="token punctuation">{</span> FromAgent<span class="token punctuation">,</span> FromCustomer <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IncidentDetailsProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">SingleStreamProjection<span class="token punctuation">&lt;</span>IncidentDetails<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">IncidentLogged</span> logged<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>logged<span class="token punctuation">.</span>IncidentId<span class="token punctuation">,</span> logged<span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> IncidentStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> Array<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Empty</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentNote<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentCategorised</span> categorised<span class="token punctuation">,</span> <span class="token class-name">IncidentDetails</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Category <span class="token operator">=</span> categorised<span class="token punctuation">.</span>Category <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentPrioritised</span> prioritised<span class="token punctuation">,</span> <span class="token class-name">IncidentDetails</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Priority <span class="token operator">=</span> prioritised<span class="token punctuation">.</span>Priority <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">AgentAssignedToIncident</span> prioritised<span class="token punctuation">,</span> <span class="token class-name">IncidentDetails</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> AgentId <span class="token operator">=</span> prioritised<span class="token punctuation">.</span>AgentId <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">AgentRespondedToIncident</span> agentResponded<span class="token punctuation">,</span> <span class="token class-name">IncidentDetails</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Notes <span class="token operator">=</span> current<span class="token punctuation">.</span>Notes<span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentNote</span><span class="token punctuation">(</span> IncidentNoteType<span class="token punctuation">.</span>FromAgent<span class="token punctuation">,</span> agentResponded<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>AgentId<span class="token punctuation">,</span> agentResponded<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Content<span class="token punctuation">,</span> agentResponded<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>VisibleToCustomer <span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">CustomerRespondedToIncident</span> customerResponded<span class="token punctuation">,</span> <span class="token class-name">IncidentDetails</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Notes <span class="token operator">=</span> current<span class="token punctuation">.</span>Notes<span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentNote</span><span class="token punctuation">(</span> IncidentNoteType<span class="token punctuation">.</span>FromCustomer<span class="token punctuation">,</span> customerResponded<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> customerResponded<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Content<span class="token punctuation">,</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentResolved</span> resolved<span class="token punctuation">,</span> <span class="token class-name">IncidentDetails</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> IncidentStatus<span class="token punctuation">.</span>Resolved <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ResolutionAcknowledgedByCustomer</span> acknowledged<span class="token punctuation">,</span> <span class="token class-name">IncidentDetails</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> IncidentStatus<span class="token punctuation">.</span>ResolutionAcknowledgedByCustomer <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentDetails</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentClosed</span> closed<span class="token punctuation">,</span> <span class="token class-name">IncidentDetails</span> current<span class="token punctuation">)</span> <span class="token operator">=></span> current <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> IncidentStatus<span class="token punctuation">.</span>Closed <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>What if you’d like to show the human-readable audit log containing the history of the incident?</strong> Not a problem! You can also reuse the events you have and just interpret them differently. For instance like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentHistory</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Description <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">IncidentHistoryTransformation</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">EventProjection</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> customerId<span class="token punctuation">,</span> contact<span class="token punctuation">,</span> description<span class="token punctuation">,</span> loggedBy<span class="token punctuation">,</span> loggedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"['</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">loggedAt</span><span class="token punctuation">}</span></span><span class="token string">'] Logged Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">' for customer '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">customerId</span><span class="token punctuation">}</span></span><span class="token string">' and description `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">description</span><span class="token punctuation">}</span></span><span class="token string">' through </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">contact</span><span class="token punctuation">}</span></span><span class="token string"> by '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">loggedBy</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>IncidentCategorised<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> category<span class="token punctuation">,</span> categorisedBy<span class="token punctuation">,</span> categorisedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">categorisedAt</span><span class="token punctuation">}</span></span><span class="token string">] Categorised Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">' as </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">category</span><span class="token punctuation">}</span></span><span class="token string"> by </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">categorisedBy</span><span class="token punctuation">}</span></span><span class="token string">"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>IncidentPrioritised<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> priority<span class="token punctuation">,</span> prioritisedBy<span class="token punctuation">,</span> prioritisedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">prioritisedAt</span><span class="token punctuation">}</span></span><span class="token string">] Prioritised Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">' as '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">priority</span><span class="token punctuation">}</span></span><span class="token string">' by </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">prioritisedBy</span><span class="token punctuation">}</span></span><span class="token string">"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>AgentAssignedToIncident<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> agentId<span class="token punctuation">,</span> assignedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">assignedAt</span><span class="token punctuation">}</span></span><span class="token string">] Assigned agent `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">agentId</span><span class="token punctuation">}</span></span><span class="token string"> to incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>CustomerRespondedToIncident<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> response<span class="token punctuation">,</span> respondedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">respondedAt</span><span class="token punctuation">}</span></span><span class="token string">] Agent '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">response<span class="token punctuation">.</span>CustomerId</span><span class="token punctuation">}</span></span><span class="token string">' responded with response '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">response<span class="token punctuation">.</span>Content</span><span class="token punctuation">}</span></span><span class="token string">' to Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>AgentRespondedToIncident<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> response<span class="token punctuation">,</span> respondedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> responseVisibility <span class="token operator">=</span> response<span class="token punctuation">.</span>VisibleToCustomer <span class="token punctuation">?</span> <span class="token string">"public"</span> <span class="token punctuation">:</span> <span class="token string">"private"</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">respondedAt</span><span class="token punctuation">}</span></span><span class="token string">] Agent '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">response<span class="token punctuation">.</span>AgentId</span><span class="token punctuation">}</span></span><span class="token string">' responded with </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">responseVisibility</span><span class="token punctuation">}</span></span><span class="token string"> response '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">response<span class="token punctuation">.</span>Content</span><span class="token punctuation">}</span></span><span class="token string">' to Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">IncidentResolved</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> IncidentId<span class="token punctuation">,</span> <span class="token class-name">ResolutionType</span> Resolution<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ResolvedBy<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ResolvedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>IncidentResolved<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> resolution<span class="token punctuation">,</span> resolvedBy<span class="token punctuation">,</span> resolvedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">resolvedAt</span><span class="token punctuation">}</span></span><span class="token string">] Resolved Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">' with resolution `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">resolution</span><span class="token punctuation">}</span></span><span class="token string"> by '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">resolvedBy</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>ResolutionAcknowledgedByCustomer<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> acknowledgedBy<span class="token punctuation">,</span> acknowledgedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">acknowledgedAt</span><span class="token punctuation">}</span></span><span class="token string">] Customer '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">acknowledgedBy</span><span class="token punctuation">}</span></span><span class="token string">' acknowledged resolution of Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IncidentHistory</span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">IEvent<span class="token punctuation">&lt;</span>IncidentClosed<span class="token punctuation">></span></span> input<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>incidentId<span class="token punctuation">,</span> closedBy<span class="token punctuation">,</span> closedAt<span class="token punctuation">)</span> <span class="token operator">=</span> input<span class="token punctuation">.</span>Data<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">IncidentHistory</span><span class="token punctuation">(</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> incidentId<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">closedAt</span><span class="token punctuation">}</span></span><span class="token string">] Agent '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">closedBy</span><span class="token punctuation">}</span></span><span class="token string">' closed Incident with id: '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">incidentId</span><span class="token punctuation">}</span></span><span class="token string">'"</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>This time, I’m using a different projection base class: <a href="https://martendb.io/events/projections/event-projections.html">Event Projection</a>. It gives you even higher flexibility.</p> <p>By generating a new id for each Incident History entry, I end up with a new record in the read model for each registered incident event. I’m also placing the incident id to be able to filter it later by that.</p> <p>Cool? Let’s go deeper, then!</p> <p><strong>What if we’d like to build a dashboard for a user containing its name, email and summaries of the incidents in a certain status?</strong> Then we’d need to combine information from two types of streams:</p> <ul> <li>customer information,</li> <li>incident data.</li> </ul> <p>For that, we need to correlate the incident and customer information. The simplest way is to define the customer dashboard id as equal to the customer id. That’s optimal because we don’t want multiple instances of this report for the same customer.</p> <p>If we had a customer id in each incident event, we could use it to find the information in each model.</p> <p>Marten also provides a base class for handling multiple streams (of the same or different type): <a href="https://martendb.io/events/projections/view-projections.html">Multi Stream Projection</a>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CustomerCreated</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CustomerId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Email <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerIncidentsSummary</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> required <span class="token return-type class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> required <span class="token return-type class-name"><span class="token keyword">string</span></span> Email<span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Pending <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Resolved <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Acknowledged <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Closed <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerIncidentsSummaryProjection</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">MultiStreamProjection<span class="token punctuation">&lt;</span>CustomerIncidentsSummary<span class="token punctuation">,</span> Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">CustomerIncidentsSummaryProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CustomerCreated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentResolved<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ResolutionAcknowledgedByCustomer<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentClosed<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">CustomerCreated</span> customerCreated<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> name<span class="token punctuation">,</span> email<span class="token punctuation">)</span> <span class="token operator">=</span> customerCreated<span class="token punctuation">;</span> current<span class="token punctuation">.</span>Name <span class="token operator">=</span> name<span class="token punctuation">;</span> current<span class="token punctuation">.</span>Email <span class="token operator">=</span> email<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentLogged</span> logged<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> current<span class="token punctuation">.</span>Pending<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentResolved</span> resolved<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> current<span class="token punctuation">.</span>Pending<span class="token operator">--</span><span class="token punctuation">;</span> current<span class="token punctuation">.</span>Resolved<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ResolutionAcknowledgedByCustomer</span> acknowledged<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> current<span class="token punctuation">.</span>Resolved<span class="token operator">--</span><span class="token punctuation">;</span> current<span class="token punctuation">.</span>Acknowledged<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">IncidentClosed</span> closed<span class="token punctuation">,</span> <span class="token class-name">CustomerIncidentsSummary</span> current<span class="token punctuation">)</span> <span class="token punctuation">{</span> current<span class="token punctuation">.</span>Acknowledged<span class="token operator">--</span><span class="token punctuation">;</span> current<span class="token punctuation">.</span>Closed<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As events may come from streams of different types, Marten won’t be able to use stream id by convention. We need to help Marten tell which field from the event will be used to find the read model record. That’s why we select identity in the projection constructor:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token function">CustomerIncidentsSummaryProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CustomerCreated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentResolved<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ResolutionAcknowledgedByCustomer<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentClosed<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>However, if you’re a careful reader (which I’m sure you are!), you noticed that we have customer id only in <em>CustomerCreated</em> and <em>IncidentLogged</em> events.</p> <p><strong>Best practice is to avoid repeating the same information in events if it’s not directly related to the operation.</strong> We might not want to repeat customer id in incident events, as it can only be assigned when the incident is logged; then, there’s no option to change it.</p> <p>Of course, a bit of redundancy may not do significant harm, as <a href="/en/events_should_be_as_small_as_possible/">events should be as small as possible, but not smaller</a>. Still, it’s a tradeoff that we should carefully choose to do or not.</p> <p><strong>What if we’d like not to repeat this data but load it when applying projection?</strong> Can Marten help with that? Of course! We can do additional preprocessing of events using the <a href="https://martendb.io/events/projections/multi-stream-projections.html#view-projection-with-custom-grouper">custom event grouper</a> feature.</p> <p>Let’s see the full implementation, then explain it step by step.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerIncidentsSummaryGrouper</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IAggregateGrouper<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Type<span class="token punctuation">[</span><span class="token punctuation">]</span></span> eventTypes <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">IncidentResolved</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">ResolutionAcknowledgedByCustomer</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">IncidentClosed</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Group</span><span class="token punctuation">(</span><span class="token class-name">IQuerySession</span> session<span class="token punctuation">,</span> <span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>IEvent<span class="token punctuation">></span></span> events<span class="token punctuation">,</span> <span class="token class-name">ITenantSliceGroup<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span> grouping<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> filteredEvents <span class="token operator">=</span> events <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>ev <span class="token operator">=></span> eventTypes<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>ev<span class="token punctuation">.</span>EventType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>filteredEvents<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> incidentIds <span class="token operator">=</span> filteredEvents<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>StreamId<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">QueryRawEventDataOnly</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>e <span class="token operator">=></span> incidentIds<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>IncidentId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>x <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> x<span class="token punctuation">.</span>IncidentId<span class="token punctuation">,</span> x<span class="token punctuation">.</span>CustomerId <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> <span class="token keyword">group</span> <span class="token keyword">in</span> result<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>g <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> g<span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> Events <span class="token operator">=</span> filteredEvents<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>ev <span class="token operator">=></span> ev<span class="token punctuation">.</span>StreamId <span class="token operator">==</span> g<span class="token punctuation">.</span>IncidentId<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> grouping<span class="token punctuation">.</span><span class="token function">AddEvents</span><span class="token punctuation">(</span><span class="token keyword">group</span><span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> <span class="token keyword">group</span><span class="token punctuation">.</span>Events<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>We start by defining for each events we’ll be running the custom transformations:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Type<span class="token punctuation">[</span><span class="token punctuation">]</span></span> eventTypes <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">IncidentResolved</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">ResolutionAcknowledgedByCustomer</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">IncidentClosed</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>For the rest, we’ll go with regular logic.</p> <p>Then we define Group method.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Group</span><span class="token punctuation">(</span><span class="token class-name">IQuerySession</span> session<span class="token punctuation">,</span> <span class="token class-name">IEnumerable<span class="token punctuation">&lt;</span>IEvent<span class="token punctuation">></span></span> events<span class="token punctuation">,</span> <span class="token class-name">ITenantSliceGroup<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span> grouping<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p>It has:</p> <ul> <li><strong>query session</strong> to enable additional loading of data when needed.</li> <li><strong>events</strong>, all the events that are handled in this scope. For synchronous projection, it will contain all events appended before saving changes; for asynchronous, it will contain a batch of processing events. Marten does processing in batches to improve performance.</li> <li><strong>grouping</strong> to add transformed events, grouping them by specified stream id.</li> </ul> <p><strong>The next step is to filter only events that we want to transform:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> filteredEvents <span class="token operator">=</span> events <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>ev <span class="token operator">=></span> eventTypes<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>ev<span class="token punctuation">.</span>EventType<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>filteredEvents<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span></code></pre></div> <p>Having them, we need to find customer ids for each of those events:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">QueryRawEventDataOnly</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>e <span class="token operator">=></span> incidentIds<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>e<span class="token punctuation">.</span>IncidentId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>x <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> x<span class="token punctuation">.</span>IncidentId<span class="token punctuation">,</span> x<span class="token punctuation">.</span>CustomerId <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re trying to find the <em>IncidentLogged</em> events that were registered for the specific incident. We assume it has to be available before resolving, acknowledging or closing the incident (as shown in the diagram above).</p> <p><strong>Why are we querying events instead of, e.g. incident short info read models?</strong> You don’t want to couple the projections processing, that will impact <a href="/en/how_to_scale_projections_in_the_event_driven_systems/">scaling</a> and create a <em>rebuild train</em> if we’re going to rebuild read models. We need to remember to process short incident info before we try to rebuild the customer summary. That’s a no-go. Especially having that Marten, for asynchronous processing, parallelises processing of multiple projection types to boost performance.</p> <p><strong>Now we can group events by the customer id:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> <span class="token keyword">group</span> <span class="token keyword">in</span> result<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>g <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> g<span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> Events <span class="token operator">=</span> filteredEvents<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>ev <span class="token operator">=></span> ev<span class="token punctuation">.</span>StreamId <span class="token operator">==</span> g<span class="token punctuation">.</span>IncidentId<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> grouping<span class="token punctuation">.</span><span class="token function">AddEvents</span><span class="token punctuation">(</span><span class="token keyword">group</span><span class="token punctuation">.</span>CustomerId<span class="token punctuation">,</span> <span class="token keyword">group</span><span class="token punctuation">.</span>Events<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>After that, when we register grouper in our projection:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token function">CustomerIncidentsSummaryProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CustomerCreated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Identity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentLogged<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>CustomerId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">CustomGrouping</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CustomerIncidentsSummaryGrouper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Then Marten will run the transformation behind the scenes and call the proper apply method.</p> <p>You can define even fancier transformations. Check <a href="/en/event_transformations_and_loosely_coupling/">Event transformations, a tool to keep our processes loosely coupled</a> to see an example of that.</p> <p>Yet, with great power comes great responsibility. You need to be careful not to compromise performance after going wild. Read more considerations in <a href="/en/i_will_just_add_one_more_field/">Anti-patterns in event modelling - I’ll just add one more field</a>. And at least register such projection as asynchronous, e.g.:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span>Services <span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>options <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentHistoryTransformation<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentDetailsProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IncidentShortInfoProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CustomerIncidentsSummaryProjection<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AddAsyncDaemon</span><span class="token punctuation">(</span>DaemonMode<span class="token punctuation">.</span>Solo<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Do you want more? Source codes are available <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/Helpdesk">here in my repository</a>.</p> <p>How to project events to other database than Postgres? I got you covered, read the <a href="/en/projecting_from_marten_to_elasticsearch">guide using Elasticsearch as an example</a>.</p> <p>Watch also the webinar:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/Lc2zV8KA16A?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>But most importantly, try it on your own, experiment and have fun!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Explicit validation in C# just got simpler!]]>https://event-driven.io/en/explicit_validation_in_csharp_just_got_simpler/https://event-driven.io/en/explicit_validation_in_csharp_just_got_simpler/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2ed90da63699cbcfbc05fa37a0447d1e/c60e9/2023-02-05-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAwX/xAAWAQEBAQAAAAAAAAAAAAAAAAACAAH/2gAMAwEAAhADEAAAAee9GmJSwj//xAAaEAACAwEBAAAAAAAAAAAAAAABAgADESMx/9oACAEBAAEFAvFRdD0qYCS9PM7P/8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQMBAT8BCf/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EAB0QAAICAQUAAAAAAAAAAAAAAAABAhIRAzFBUXH/2gAIAQEABj8CbN8iouB6cnaJZdYPT//EABoQAQADAQEBAAAAAAAAAAAAAAEAESExQaH/2gAIAQEAAT8hb9SAOnVuRShXWvYvKgxmCjr7irV7qf/aAAwDAQACAAMAAAAQkD//xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAIAQMBAT8QpiP/xAAWEQADAAAAAAAAAAAAAAAAAAAAARH/2gAIAQIBAT8QbhT/xAAcEAEAAgIDAQAAAAAAAAAAAAABABEhMUFRcfD/2gAIAQEAAT8Qx+lKxrcSBHASnH3kQGgDcnOVIwIBa7cBC2UgCquki99S3QoutZn/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/2ed90da63699cbcfbc05fa37a0447d1e/c60e9/2023-02-05-cover.jpg" srcset="/static/2ed90da63699cbcfbc05fa37a0447d1e/37402/2023-02-05-cover.jpg 200w, /static/2ed90da63699cbcfbc05fa37a0447d1e/4cda9/2023-02-05-cover.jpg 400w, /static/2ed90da63699cbcfbc05fa37a0447d1e/c60e9/2023-02-05-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Validation is a thriving concept. It enables incredible creativity in developers implementing it in various ways.</strong> I explained my general take on <a href="/en/how_to_validate_business_logic/">How to validate business logic</a>. This time, let’s look at how we could simplify our approach in C# language.</p> <p><strong>The typical flow of <em>Line of business</em> web application is:</strong></p> <ul> <li>User fills a form data in the UI (e.g. web application),</li> <li>Web application serialises it as the web request and calls the Web API endpoint,</li> <li>Web API parses data and runs business logic.</li> </ul> <p>On each step, we should perform validation.</p> <p><strong>Let’s focus on the last step for now. In ASP.NET, the validation is quite often conflated with parsing and <a href="https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-8.0">model binding</a>.</strong> Historically, .NET devs got accustomed to doing all at once. It may sound like a brilliant idea, but it’s also a pit of performance issues and nasty production bugs to debug. It all goes well while we remember the conventions and follow them precisely. Yet, the number of permutations we may find makes it hard to tame.</p> <p>The potential solution could be explicit validation, so break the exact flow into <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">parse first, then validate</a>. If you have tried that already, you may think I’m suggesting you become a caveman and do a lot of copy-pasting. Also, you may ask how to achieve human-friendly error messages.</p> <p>And yeah, I’ve been there and struggled with those issues. But I learned from it. Plus, new stuff in .NET can make this more accessible.</p> <p>Let’s say we’d like to register a new Product in our product catalogue. We may want to have it strongly typed, like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RegisterProduct</span><span class="token punctuation">(</span> <span class="token class-name">ProductId</span> ProductId<span class="token punctuation">,</span> <span class="token class-name">SKU</span> SKU<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductId</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> Value<span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">SKU</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> Value<span class="token punctuation">)</span></code></pre></div> <p><strong>I’m using <a href="/en/notes_about_csharp_records_and_nullable_reference_types/">records and nullable types</a>, as they’re a decent way for modelling data transfer objects.</strong></p> <p>Yet, they, unfortunately, we can still provide wrong values to our types, e.g.:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> validSKU <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SKU</span><span class="token punctuation">(</span><span class="token string">"ZS1023"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> forcedNull <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SKU</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> wrongSKU <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SKU</span><span class="token punctuation">(</span><span class="token string">"wrong format"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>What to do, then? We can use the Smart Constructor pattern presented in the <a href="/en/how_to_validate_business_logic/">previous post</a>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">SKU</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> Value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">SKU</span> <span class="token function">From</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> sku<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>sku<span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span>Regex<span class="token punctuation">.</span><span class="token function">IsMatch</span><span class="token punctuation">(</span>sku<span class="token punctuation">,</span> <span class="token string">"[A-Z]{2,4}[0-9]{4,18}"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SKU</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>That can help us to validate SKU creation with the following:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// both calls will throw ArgumentException</span> <span class="token class-name"><span class="token keyword">var</span></span> forcedNull <span class="token operator">=</span> SKU<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> wrongSKU <span class="token operator">=</span> SKU<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span><span class="token string">"wrong format"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Thanks to <em>nameof(sku)</em>, we’ll get information on which field was wrong. We can also map <em>ArgumentException</em> by convention to <em>400 Bad Request</em> HTTP status.</strong></p> <p>It won’t guard against such calls:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> worseSKU <span class="token operator">=</span> sku <span class="token keyword">with</span> <span class="token punctuation">{</span> Value <span class="token operator">=</span> <span class="token string">"definitely wrong value"</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> theWorstSKU <span class="token operator">=</span> sku <span class="token keyword">with</span> <span class="token punctuation">{</span> Value <span class="token operator">=</span> <span class="token keyword">null</span><span class="token operator">!</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>To do that, we’ll need to implement SKU as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">SKU</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Value <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">JsonConstructor</span></span><span class="token punctuation">]</span> <span class="token keyword">private</span> <span class="token function">SKU</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> Value <span class="token operator">=</span> <span class="token keyword">value</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Deconstruct</span><span class="token punctuation">(</span><span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span> <span class="token operator">=</span> Value<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">SKU</span> <span class="token function">From</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> sku<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>sku<span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span>Regex<span class="token punctuation">.</span><span class="token function">IsMatch</span><span class="token punctuation">(</span>sku<span class="token punctuation">,</span> <span class="token string">"[A-Z]{2,4}[0-9]{4,18}"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SKU</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>By hiding the public constructor, we’re disabling the usage of <em>with</em>, keeping <a href="https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/functional/deconstruct#user-defined-types">the deconstruction</a> capabilities, and we also enable proper deserialisation.</p> <p><strong>That’s a lot of code, and maybe it’s better to keep the convention that we’re calling Smart Constructor in our code and leave with that tradeoff.</strong></p> <p>Still, if we’ll need to write such copy-and-paste validation logic, it would quickly become tedious and, thus: error-prone.</p> <p>Luckily, as promised, we could do better than that. We’ll use two techniques to achieve it:</p> <ul> <li><a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/caller-argument-expression?source=recommendations">Caller Argument Expression</a>,</li> <li><a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/nullable-analysis">Attributes for null-state static analysis</a>.</li> </ul> <p>Have a look at the following method:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Validation</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">NotNull</span></span><span class="token punctuation">]</span> <span class="token keyword">this</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerArgumentExpression</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> argumentName <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">!</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">value</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span>argumentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s an extension method that takes a nullable <em>string</em> value and checks if it’s not null or empty, then returns not null value. It’s a classical guard method.</p> <p>What’s unusual is that we’re using cryptic <em>[CallerArgumentExpression(“value”)] string? argumentName</em> syntax. It tells the compiler that it should try to resolve the argument name of the parameter <em>value</em> from the caller context.</p> <p>If we use it as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> sku <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> sku<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then it’ll automatically use the <em>sku</em> as the parameter name for the <em>ArgumentException</em>. That’s not all!</p> <p>If we do:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> sku <span class="token operator">=</span> <span class="token string">"ZS1023"</span><span class="token punctuation">;</span> sku<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> verifiedSku <span class="token operator">=</span> sku<span class="token punctuation">;</span></code></pre></div> <p><strong>Then using the <em>NotNull</em> argument tells the compiler that if this method runs successfully, the variable is not null.</strong></p> <p>Note that this will work only for the nullable reference type. For the value types like Guid, we’ll need to use the returned value from the method, but that’s also neat, isn’t it?</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Validation</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Guid</span> <span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">NotNull</span></span><span class="token punctuation">]</span> <span class="token keyword">this</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerArgumentExpression</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> argumentName <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token keyword">value</span> <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value <span class="token operator">!=</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span>argumentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> productId <span class="token operator">=</span> <span class="token string">"ZS1023"</span><span class="token punctuation">;</span> <span class="token comment">// this will work</span> <span class="token class-name">Guid</span> verifiedProductId <span class="token operator">=</span> productId<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// this won't compile</span> <span class="token class-name">Guid</span> verifiedProductId <span class="token operator">=</span> productId<span class="token punctuation">;</span></code></pre></div> <p><strong>We can compose those methods and build simple but powerful fleet of validation methods:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Validation</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Guid</span> <span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">NotNull</span></span><span class="token punctuation">]</span> <span class="token keyword">this</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerArgumentExpression</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> argumentName <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token keyword">value</span> <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value <span class="token operator">!=</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span>argumentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">NotNull</span></span><span class="token punctuation">]</span> <span class="token keyword">this</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerArgumentExpression</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> argumentName <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token operator">!</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">value</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span>argumentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> <span class="token function">AssertNullOrNotEmpty</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerArgumentExpression</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> argumentName <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">?.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span>argumentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">AssertMatchesRegex</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">NotNull</span></span><span class="token punctuation">]</span> <span class="token keyword">this</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">StringSyntax</span><span class="token attribute-arguments"><span class="token punctuation">(</span>StringSyntaxAttribute<span class="token punctuation">.</span>Regex<span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span></span> pattern<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerArgumentExpression</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> argumentName <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> Regex<span class="token punctuation">.</span>IsMatch<span class="token class-name"><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span>AssertNotEmpty<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pattern<span class="token punctuation">)</span> <span class="token punctuation">?</span></span> <span class="token keyword">value</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span>argumentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> <span class="token function">AssertPositive</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">NotNull</span></span><span class="token punctuation">]</span> <span class="token keyword">this</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerArgumentExpression</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> argumentName <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">?.</span><span class="token function">AssertPositive</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span>argumentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> <span class="token function">AssertPositive</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name"><span class="token keyword">int</span></span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">CallerArgumentExpression</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"value"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> argumentName <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span> <span class="token operator">></span> <span class="token number">0</span> <span class="token punctuation">?</span> <span class="token keyword">value</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span>argumentName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Then our final mapping with smart constructors can look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">readonly</span> <span class="token keyword">record</span> <span class="token class-name"><span class="token keyword">struct</span></span> <span class="token function">ProductId</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> Value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductId</span> <span class="token function">From</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> productId<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>productId<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">readonly</span> <span class="token keyword">record</span> <span class="token class-name"><span class="token keyword">struct</span></span> <span class="token function">SKU</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> Value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">SKU</span> <span class="token function">From</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> sku<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>sku<span class="token punctuation">.</span><span class="token function">AssertMatchesRegex</span><span class="token punctuation">(</span><span class="token string">"[A-Z]{2,4}[0-9]{4,18}"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RegisterProduct</span><span class="token punctuation">(</span> <span class="token class-name">ProductId</span> ProductId<span class="token punctuation">,</span> <span class="token class-name">SKU</span> SKU<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">RegisterProduct</span> <span class="token function">From</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> sku<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> description<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> ProductId<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">,</span> SKU<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">,</span> name<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> description<span class="token punctuation">.</span><span class="token function">AssertNullOrNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Then we can use it in the endpoint as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">endpoints<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span> <span class="token string">"api/products/"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token class-name">ProductsDBContext</span> dbContext<span class="token punctuation">,</span> <span class="token class-name">RegisterProductRequest</span> request<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span> <span class="token operator">=</span> request<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> productId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> RegisterProduct<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>productId<span class="token punctuation">,</span> sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">Handle</span><span class="token punctuation">(</span> dbContext<span class="token punctuation">.</span>AddAndSave<span class="token punctuation">,</span> dbContext<span class="token punctuation">.</span>ProductWithSKUExists<span class="token punctuation">,</span> command<span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Created</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/products/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span>StatusCodes<span class="token punctuation">.</span>Status201Created<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span>StatusCodes<span class="token punctuation">.</span>Status400BadRequest<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// we're embracing here that we can expect anything from the UI</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RegisterProductRequest</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> SKU<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>The main downside of this approach may be that it’s <em>fail fast</em>.</strong> It’ll throw an exception on the first wrong property. We won’t get a listing of all the incorrect values. Still, that will be fine for the UI-based requests, as we should have validations made there. If the request bypassed them, it either means we forgot to align them or someone made something malicious. For the API-first design, that may be a more significant issue if our convention is to return all errors instead of failing fast.</p> <p><strong>I believe that such an explicit approach is much safer and clearer.</strong> We’re depending on the compiler, getting failures during development, not in runtime. We’re making <a href="/en/explicit_events_serialisation_in_event_sourcing/">explicit things that should be explicit</a> and have control over what’s happening.</p> <p>There are no redundant allocations, and no reflection magic, which is an obvious win.</p> <p><strong>Keeping validation in the code also creates a better form of documentation, as looking at the class, we see what to expect.</strong> Once we create an instance of an object, we can trust it and don’t repeat validation in multiple places.</p> <p>Of course, such an approach requires consistency and consideration in the types of design, but if we keep it stupidly simple, then such composition can take us pretty far and reduce the headache of unpleasant surprises on production.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Stacking the bricks in the software development process]]>https://event-driven.io/en/stacking_the_bricks/https://event-driven.io/en/stacking_the_bricks/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/153581fcbd8de709ca1ea147d86b316c/c60e9/2023-01-29-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAECBAX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHrToRYw//EABsQAAMAAgMAAAAAAAAAAAAAAAECAwAQERIT/9oACAEBAAEFAnr1N7eWA8gopLTVtf/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABoQAAICAwAAAAAAAAAAAAAAAAABESEQMTL/2gAIAQEABj8C5kVbJJaLx//EABoQAQEBAAMBAAAAAAAAAAAAAAERABAhMWH/2gAIAQEAAT8haYgJXIJC/XmAA0dPFZMAAs694//aAAwDAQACAAMAAAAQw8//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAaEAEBAQEBAQEAAAAAAAAAAAABEQAhQTGR/9oACAEBAAE/EFJAAZ1kO+tJ3dCmEf0xIgURpkS7RT6ZP4MdWSRfcAEOBv/Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/153581fcbd8de709ca1ea147d86b316c/c60e9/2023-01-29-cover.jpg" srcset="/static/153581fcbd8de709ca1ea147d86b316c/37402/2023-01-29-cover.jpg 200w, /static/153581fcbd8de709ca1ea147d86b316c/4cda9/2023-01-29-cover.jpg 400w, /static/153581fcbd8de709ca1ea147d86b316c/c60e9/2023-01-29-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>We, developers, are <a href="/en/holy_graal_syndrome/">searching for Holy Grail</a>, one pattern to rule them all. There’s no such.</strong></p> <p>In his excellent book <a href="https://jamesclear.com/atomic-habits">“Atomic Habits”</a>, James Clear described how we should stack our habits.</p> <p><strong>If we want to change our bad habits, we should create a helpful environment for better ones.</strong> For instance, put our mobile far from our hands to avoid being tempted to check it too often.</p> <p>In the same way, we should build our dev environment from small habits and good practices to enable good behaviours and make it harder to continue bad habits. That means <a href="/en/how_to_effectively_compose_your_business_logic/">focusing on composability</a>, on being able to remove specific components without breaking the whole system. On making our dev life efficient and limiting distractions.</p> <p><strong>I’m thinking about good habits as some form of Kata; by doing them, we’re making those practices commodity.</strong> By that, we don’t need to think about how we’re doing it, but as we’re doing that subconsciously, using muscle memory.</p> <p>That reduces the number of things we need to remember reducing the cognitive load. Thanks to that, we can get more focus on the most important things. So, for instance, the bigger picture.</p> <p>Of course, there’s a risk when we forget that those small things are just bricks to build something bigger, and we need to keep an eye on that.</p> <p><strong>What small things then? For instance:</strong></p> <ul> <li>understanding that <a href="/en/generic_does_not_mean_simple/">generic doesn’t mean simple</a>,</li> <li>cutting the number of layers in our architecture to <a href="/en/what_does_mr_bean_opening_the_car_have_to_do_with_programming/">not block the thought process</a>,</li> <li><a href="/en/how_to_slice_the_codebase_effectively/">keep the things that are changing together in one place</a>, e.g. by slicing the code by business behaviour and feature folders. It’ll reduce the context switching,</li> <li><a href="/en/removability_over_maintainability/">focus on the removability instead of maintainability</a>,</li> <li><a href="/en/should_programmers_productivity_be_shown_in_code_formatting/">reduce meaningless discussion and creativity where it’s not needed</a>. E.g. define conventions for formatting, automate them and stick to them by making them a commodity,</li> <li>don’t take <a href="/en/what_does_a_construction_failure_have_to_do_with_our_authorities/">blindly advice from authorities</a>. Be pragmatic, understand the context you’re in and in which <a href="https://event-driven.io/en/the_magic_is_that_there_is_no_magic/">patterns were designed</a>. That will help you to be <a href="/en/when_agile_is_not_enough/">agile in the right way</a>.</li> </ul> <p>It’s essential to have tools to zoom in and zoom out. We need to have a vision of the solution, but then you need to execute it. A grand vision with poor execution won’t succeed. The same thing goes otherwise. I’m a fan of proof of concepts, so I do a quick dive into the code/tools to evaluate assumptions and ensure that they’re not show-stoppers before moving forward.</p> <p><strong>Ergonomy is underestimated in our industry.</strong> Houses are made from bricks. In the same way, we should be stacking small patterns and practices matching specific needs.</p> <p>That’s also aligned with the <a href="https://en.wikipedia.org/wiki/Kaizen">Kaizen</a> concept, so continuous improvement on the process instead of trying to design one that will stay forever as designed.</p> <p><strong>Don’t think then on Clean, Hexagonal, or Whatever Architecture, but how to make your development and deliver as smooth as possible, limiting distractions.</strong></p> <p>Enhance <a href="/en/small_rant_about_software_design/">accessibility</a>, work on ergonomy, and be consistent. Stack the bricks one by one. Start with small things. Big will come.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Guide to Projections and Read Models in Event-Driven Architecture]]>https://event-driven.io/en/projections_and_read_models_in_event_driven_architecture/https://event-driven.io/en/projections_and_read_models_in_event_driven_architecture/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/505e8cec2615da883c59ff655ba4437d/c60e9/2023-01-20-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEA/9oADAMBAAIQAxAAAAHtTpCUC//EABgQAAMBAQAAAAAAAAAAAAAAAAEQEQAh/9oACAEBAAEFAj3SMr//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAWEAADAAAAAAAAAAAAAAAAAAAAICH/2gAIAQEABj8CIv8A/8QAGRAAAgMBAAAAAAAAAAAAAAAAAREAELEh/9oACAEBAAE/IQAI7AHWqUBKv//aAAwDAQACAAMAAAAQl/8A/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHBABAAICAwEAAAAAAAAAAAAAAQARIYExQZFR/9oACAEBAAE/EFD4+FIUKo95X7NzTyEKBV8VEpn/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/505e8cec2615da883c59ff655ba4437d/c60e9/2023-01-20-cover.jpg" srcset="/static/505e8cec2615da883c59ff655ba4437d/37402/2023-01-20-cover.jpg 200w, /static/505e8cec2615da883c59ff655ba4437d/4cda9/2023-01-20-cover.jpg 400w, /static/505e8cec2615da883c59ff655ba4437d/c60e9/2023-01-20-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>If I had to choose the killer feature of Event Sourcing, I’d select projections.</strong> Why? I’ll explain that in detail in this article.</p> <p>Events are facts; they represent something that has happened in the past. Projections are a different interpretation of the set of facts. Sounds enigmatic? Let’s have a look at this picture.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8e06b2e04d4e310637d16cf6532b64b6/a331c/2023-01-20-projection-01.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACnElEQVQozyWOW0hTYQCAz0vb8iFxkAjiwDdFQad4SdjLejHmw2BPzbapM2jog+ib9uK6WCARSITlMiwCcXljRolUtDQ118HLGtnadHPbuf3nP+c/5+ycmevE6uN7/z4MIpkTlF9HqYXlle0d/PuPKL4XjsaPMxRMk1DK5XN5NXuq5s5USclLyh9eVCCSIMrSrIixvESxQiLN4HuRw1hybn5p/MFD3/TM7NzC1PTMo8dPFpcDq2sfAm/ezS+t+J6/CG5sQyQzADFQxDI0T9A8w2UBLyP5rMd9vbT0otHYcKm1taPD4nQ6Lxcwt7W1mUym4uIL3jv3lLyaJgDFChgNRcBnWSQznCTK+a5ud4m+pKampra29qrdPur1Wq3W6urqurq65uZmfUnJ2P3x36qaJlgKIIyi+USajieJeJIAnOTq6ikqKqqsrDQYDBaLxefzWa3WsrKylpYWo9F4XqcbvXX3TFUJClIswk6OUptbofWtUAjfS6ap7p5ejeZceXm5Xq+32Wx+v/+aw6HRaOrr65uamrRarff2mKqqJM0Xto9TVOQwlqEhFBRRyds7HTqdtqLC0NDQYLPZTCaT2Wxub7/i6nJVVVVhGPa/nCJYEggYBUQKigSDMhRHQ+np1LOBgYH+/n6PxzM0NNTY2NjX1zcycrPT3um54el1u1++mpVOVZLmCmUSCASDSIAoVgCchOO7S4uLk5OTI8PDQ4ODvb1ul8vpdDodDsfExMRKIBA7SkAhR7MCzQoYAwTqnwTDk0A4iBxufw19w/f2w5H9cORnNOZ/vfB2dW3jy1bw88an4Dq+G06RkGIQySDsIEHHEpkMxbJIZvksQDLgZRYpnKDAgjmS4U4IENoNr2/u7OAH7z8Go/ETyMskQH8Bxl+5TP7ja+4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="projection" title="projection" src="/static/8e06b2e04d4e310637d16cf6532b64b6/a331c/2023-01-20-projection-01.png" srcset="/static/8e06b2e04d4e310637d16cf6532b64b6/36ca5/2023-01-20-projection-01.png 200w, /static/8e06b2e04d4e310637d16cf6532b64b6/a3397/2023-01-20-projection-01.png 400w, /static/8e06b2e04d4e310637d16cf6532b64b6/a331c/2023-01-20-projection-01.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>It shows the result of a boxing fight. Even if you’re not a boxing fan, you can see that the triumphing guy is Muhammad Ali. Below him lies Sonny Liston.</p> <p>Why am I telling you about it?</p> <p><strong>Because it shows pretty well what’s an event.</strong> The fight result is a fact. It cannot be retracted. It happened on 25th May 1965, so at a certain point in time. It has specific information about what has happened, like information of:</p> <ul> <li>who fought,</li> <li>that it only lasted 1 minute 44 seconds,</li> <li>the venue was Central Maine Youth Center in Lewiston, Maine,</li> <li>etc.</li> </ul> <p><strong>It also shows well what’s projection.</strong> The same result of the fight may be interpreted differently: Muhammad is triumphing, Sonny not so much. What’s more, to this day, boxing fans are arguing if this was a real fight or a rigged one. The punch knocked down Sonny Liston is known as <a href="https://en.wikipedia.org/wiki/Muhammad_Ali_vs._Sonny_Liston#The_phantom/anchor_punch">phantom punch</a> as no one saw it reaching Sonny Liston’s face.</p> <p><strong>In projections, we’re taking a set of events representing various facts (about the same object, multiple), correlating them and building interpretation.</strong> Most of the time, we store the result as a materialised read model. Yet, the projection result can be anything: text file, Excel spreadsheet, PDF, in-memory data etc.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3f3bf31c85c37fad4ca4520e6dadda98/a331c/2023-01-20-projection-02.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB7ElEQVQozy1SyW4UQQzt/7/wAYgDVyQgi0iUiAAJCaOIMNnFZDJbarq6qmyXXUvXNOKAUEWxLNkHb+89N5IGSYPzCXySWDiWzd9/F5fX27t7X09+HH8fnZyOLi7v1hokDQYD+hTShmNBzo2XfqnM2pDqsANBzq2h0fn4w87e6zdvP25/2trZ2z88urr5vbbeUuqcWIrEGblvWsN7B0f7h18+fzttLTtKrfXGCXCmUCgU4Azct5Y7JyTF+WQpOozgUwPcn41+vnu/dXx2Dtw7n52v1Sg9SgHpgbOl5CiBzwaDo2ggOAy1GZ+HtYaQo4TEEoETSV3oqA5C7h3nzpFIiDH1ue+MtRgqZo4bTgOz1/Pb1fRaz+87o4wXiUMpf2IeOA0oPflQSiEiCUFCxHpdbsCniopYq/lq8aBWM4ukrFXWHNzo8Rw59BqDNrBc2+lCz5ad0tZRsBgbS8lgckB6OXm8Hz893hpnlIWHp+mr/V+7F0tHMGtbpc3jyk0WZrIwSjvk5HxurM+WMiCDUdA9kVUW3Mp0yrrxzE0UoWQNASrkDFQTz4L1L3JTA/eAuJrePtyNl5OrtW67ymoiyfAsjMGoDWzKizkg48RRau4ULlTXOV9pSwOFglJ8LD5uqoeXhELVDCU/H5yQ60f+B8DKUvFV23X5AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="projection" title="projection" src="/static/3f3bf31c85c37fad4ca4520e6dadda98/a331c/2023-01-20-projection-02.png" srcset="/static/3f3bf31c85c37fad4ca4520e6dadda98/36ca5/2023-01-20-projection-02.png 200w, /static/3f3bf31c85c37fad4ca4520e6dadda98/a3397/2023-01-20-projection-02.png 400w, /static/3f3bf31c85c37fad4ca4520e6dadda98/a331c/2023-01-20-projection-02.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Events are facts and, thus, an excellent source for data exploration. Projections, together with analytics, and data drilling, can bring valuable insights. For instance, by checking products bought in the e-commerce system, we can find the gaps like abandoned carts, build a recommendation engine, etc. Read more in <a href="/en/never_lose_data_with_event_sourcing/">Never Lose Data Again - Event Sourcing to the Rescue!</a>.</p> <p>As mentioned, a single fact can have multiple interpretations; events can be a source of numerous projections. For instance, information about the product added to the shopping cart can trigger the contents view update, change the product availability, and feed the information out for analytics.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ea2769bcb12f3f79baf5ec98c133ffe3/a331c/2023-01-20-projection-04.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB9ElEQVQozy2SW2sUQRCF5///BfFBFAQfNIkwBDHEJJpFWbMJu5qd7M7OzqWnL9VVfZ8VH0R7EeqhaU6fU/VVF8YfjJskeoXB+EQuTb//zBcPp+/Ly+vbq5vZ1efZfLHqmSI3cbCA3viJXFIUCrSpY6oboWPQc1QUBq5nX+/enpXPnr94d1qenJXnHy7ul2smjcQwKiO00xSAQiF0uF9W5fnFx8ubuh0VhoHjKK2iqG3SNkkMQHHf8c2uZwKl9hycBKvQF2DSfLF69frNyVlZ75k2SaIHimAimJQtKHGwdSvWNdu2Qh29JDiJvlAmzhfLTzez6y/ffqxrdIccZYKkoE3UJnBNrRCPdb9c76tmFOiyL4acTG4C9NpGMEGbYMNk3ERusuEX07ERVqLVNg0cmo61TKBN5CZtYgYmtVcU1DFHDk1TPew3q35kQFh+r15eVRKxHcWuV9tWbjsltFMUBDihfQYmwAudZ2ACumHsmeBgFNplo24fhxE0B/vUjD83/XrHOOTHCoPEUAgMQvu8ZwraJnQHdNN/VCYC+V4oBrjvxdN23w5CUQATNUWFoYDj95DocyfgpPYiVz7zYwlwHVfVji2rdtupUVmpvTwqi1ULdTeOEslN6BJkEnnD5BL5fEN+AptWT+3darOuB7Qxy8j/o/0XFRdRoRb6SJAAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="projection" title="projection" src="/static/ea2769bcb12f3f79baf5ec98c133ffe3/a331c/2023-01-20-projection-04.png" srcset="/static/ea2769bcb12f3f79baf5ec98c133ffe3/36ca5/2023-01-20-projection-04.png 200w, /static/ea2769bcb12f3f79baf5ec98c133ffe3/a3397/2023-01-20-projection-04.png 400w, /static/ea2769bcb12f3f79baf5ec98c133ffe3/a331c/2023-01-20-projection-04.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>The most popular approach to handling them is to perform the so-called <em>left-fold</em> approach.</strong> We’re taking the current state of the read model, applying the upcoming event and getting the new state as a result.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/67c4cb591797c466195864b224e261bd/a331c/2023-01-20-projection-03.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB7UlEQVQozy2Sy2/TQBCH/f+fuCMOnJFoGyCVihCiVC2F1m2i0BI3aWwn9u7Oa70P24EDQlsqzWE0mvnm8ZvM+tH60bAH9tZFcf345+9VPjt6d/z59PzL14vTs4urm8W2AetHhR2y7/wgrkcJGUlYl21jpGqwBQsSdorOL/ODyfTlq9cHR9PDyfT45NPt/H6rWJNvwWpyJAElZo+Vfj/9eDiZvnk7WW0akLhT/J9CtqeuNxJR4lZxYyxKNORbdAY7YJ+BjZtaX1zmv1YVSDQcDCcq2oi2B4kgQZNHiSR9CqZINOhMKk55fRd+d75n69g6EE+pLBh6BoGN1A2JxQEkJAQH4JCJG9jtCaFZz6ti1qwXraoVW+vGvt+7MLAburDfKSq3ugt78YP4kWyaMQP2IAFQmnpVPS7rcqWBaq1rrU7mTb5GsqnVbNme3Wy/L9q8oB93ar4ShT7T5BUFA9CU9w8/r6tiroyqNRbVw4vp9YerjUHYtKoBRtuTTSIVd2eb4huwyzQHTQGQUVXQlqRrDaZUba1NvjbLLaF4nb4goATuIneRxBJL2jmtLhEAymK+XOSb5e222bXYGfZkA7DX6Eyazil8tqQIeUM+W9T4WLetYfFjukTXox3Y9ezTqZLjBu56eXLk6bdSTlLU/wNU51MeMbANqAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="projection" title="projection" src="/static/67c4cb591797c466195864b224e261bd/a331c/2023-01-20-projection-03.png" srcset="/static/67c4cb591797c466195864b224e261bd/36ca5/2023-01-20-projection-03.png 200w, /static/67c4cb591797c466195864b224e261bd/a3397/2023-01-20-projection-03.png 400w, /static/67c4cb591797c466195864b224e261bd/a331c/2023-01-20-projection-03.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Of course, nothing stops you from batch processing by taking all the events, merging them and storing the result.</strong> For instance, if you’re doing set-based aggregations like the total of sold products, money accrual, and average grades in school, it could be more efficient if done <em>en masse</em>.</p> <h2 id="projections-can-be-synchronous-and-asynchronous" style="position:relative;"><a href="#projections-can-be-synchronous-and-asynchronous" aria-label="projections can be synchronous and asynchronous permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Projections can be synchronous and asynchronous</h2> <p>Although most tooling shows that they need eventual consistency, that’s incorrect. Their state doesn’t have to be updated with a delay. It’s a technical <em>“detail”</em> of the exact implementation. If you’re storing events and read models in the same relational database (like e.g. <a href="https://martendb.io/">Marten</a> in Postgres), then you can wrap all in a transaction. Then either events are appended and projections updated, or no change is made.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/675be49499359f862c1a42a1dc4025f7/a331c/2023-01-20-projection-05.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB1UlEQVQozy1Sy24TQRDc//8GJC6cuBDiYKNIEQrOgyjvxDiyMSTOemempx/z3pU4IBgbqTWHnqqamupuXBwk9MDRcnShSCjD7z9Xt/cfR+MvX8+Op+fHJ+dXt7O2sy4OGj1y9LGXUFBSI75sNL0qfFW4MYySN5rOLq4/jCZv3r7bO/i8N5pMDo/uHp5aw4aiss5QIEkouVHW73863NsfH4wPp2eXVtJGs7YekI3WSnVgybq+1dyBQ8mGosYA6C3HBl2+uZ+ffrs+Ob98mC/JFaCErqj2eTW/WT5eqK4FzugSuYIuo2QrGSjAjky+kOvJFfIFJdeOK1biizbT7y8gFYAuAycrtaoEp/qyhJ5D4f9nrYrgtAFYa3x/uugwYOVE9kVCv8X35LKV1Nhtzrtr4AiSlEYWAYEYEzlvxYaYNKChgJVTMYABKDSGksZoKBiqZPSlUwAWjIW+9MPQk3DOBdkp6w1FQ7E6r7ZTYzjtaLUlicPQdsa2T76bxRg1yO1sdTdfLVZrqLBkd6Fsv9bsNKBKBEMBJG8UULdM9GsYBpT4uHiZ/1ivnlu9nbCphqPB6rSZrfHnq1LAEmtm5IuyApaJvTiPJIBsyWkg8lm2u0Xb9fo3qr+SqlX2G5zbNwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="projection" title="projection" src="/static/675be49499359f862c1a42a1dc4025f7/a331c/2023-01-20-projection-05.png" srcset="/static/675be49499359f862c1a42a1dc4025f7/36ca5/2023-01-20-projection-05.png 200w, /static/675be49499359f862c1a42a1dc4025f7/a3397/2023-01-20-projection-05.png 400w, /static/675be49499359f862c1a42a1dc4025f7/a331c/2023-01-20-projection-05.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Of course, we should not be afraid of eventual consistency.</strong> How soon is now? There’s no now! Everything in the real world happens with some delay. Also, if we want to make a reliable and fault-tolerant processing pipeline doing stuff as a background process may be a go-to way. Read more in <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox, Inbox patterns and delivery guarantees explained</a>.</p> <p><strong>Projections are a concept that’s part of Event-Driven Architecture and essential building block of <a href="/en/cqrs_facts_and_myths_explained/">CQRS</a>. They’re not tied to Event Sourcing.</strong> Yet, event stores can help you in making them efficient. Most of the time, they allow you to subscribe to the appended events notifications, for instance, <a href="/en/integrating_Marten/">Marten’s Async Daemon</a> or <a href="/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/">EventStoreDB subscriptions</a>. They are an <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">outbox pattern</a> provided as a commodity by the database. That gives both the durability for the events and strong guarantees around processing them.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/112c60a0d43a8ba3dab0ae97d43f8f62/a331c/2023-01-20-projection-06.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB4UlEQVQoz32RW08bQQyF9///g0p96GulogZRIi4FiYIKlUABKRTRhMsum92Z8dgzntm5bEKTNtA+ttZ5sCx99tFxYVwijhJZoTMciEOcLy9GV4Ot4eGXr0fHp0cnp+ej8VOjjEsCrEJnXSQOQL4A8mWtaoFVA7Ugia5q4OTsYmNz+83bdxub2x83h8Pdg9HVTdViCzyTpgUL5IG64r4S7z8MtoZ72zufH6rWh4zGGRe4y+wzd9m4xD5LbRWy9Yk4oO2QnERXSO32Do42Bp/2D4/JeBcW2mZlU0jz1b8rpSw0F9qGmTST+7oW5LqsKPk4R17z/eJnF/vUL+bPy9KU4+Y6PodXOKcsNRfEkThx1yNHF9JMxxZjo0Op4rT1o4mcNmzy8uzucvfy0Pb8F+6ldoVCp9ABeaG5C+mm4p1vD/sXlaC0Wq18nM8w1hDGpbmtdYOhhgAmdj4K4EJo34IT2jXArovfS7t//nhyLRodV6tlOdPTJ5yWclLC5F7MhLl9UD8ehdLre4VEL/U6OqFdFzOHhbIZ/SL2z/8JLOe5RF8AeoVeomuBpbZkLGir0RrLSBbJajQvsposmj9DAFrbHldwVzWNJOMimK4BbsEKZIlO0Xrpi/i1UeTVi0cB9veffwHUYVx8ZZx33gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="projection" title="projection" src="/static/112c60a0d43a8ba3dab0ae97d43f8f62/a331c/2023-01-20-projection-06.png" srcset="/static/112c60a0d43a8ba3dab0ae97d43f8f62/36ca5/2023-01-20-projection-06.png 200w, /static/112c60a0d43a8ba3dab0ae97d43f8f62/a3397/2023-01-20-projection-06.png 400w, /static/112c60a0d43a8ba3dab0ae97d43f8f62/a331c/2023-01-20-projection-06.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="projections-rebuild" style="position:relative;"><a href="#projections-rebuild" aria-label="projections rebuild permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Projections rebuild</h2> <p><strong>The significant benefit of the projections is that they’re predictable.</strong> That means that the same logic will generate the same result for the same events.</p> <p><strong>As events are our source of truth, then we can think of read models as secondary data.</strong> If we treat events as the source of truth and base projection processing only on data we get from them, we can also rebuild our read models.</p> <p>We could break it into two phases:</p> <ul> <li><strong>catching up</strong>, where we’re far from the current state.</li> <li><strong>live</strong>, where we’re processing new, upcoming events.</li> </ul> <p><strong>How to decide if the gap is <em>“small enough”</em>?</strong> We might never be fully caught up if events are appended continuously. Because of that, we need to define some threshold, e.g. in our context, live means that we have a maximum of ten events to process from the latest registered event. Of course, this can be done case by case and differ for various projections. Read also more in <a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>.</p> <p>We can use different projection handling techniques depending on the phase we’re in, e.g., batch processing when we’re catching up and left-fold when we’re live.</p> <p>Projections rebuilds are usually made asynchronously, while the left-fold approach can work well in sync and async ways.</p> <p><strong>The simplest rebuild we can do is truncate the current state of the read model and then reapply all events through projection logic.</strong> We can do that if we can afford the downtime. There will be a time when there’s no data, or we’re far from being up to date.</p> <p><strong>If we cannot afford downtime, we can do a <em>blue-green</em> rebuild.</strong> In that, we’re setting up a read model in the other storage (e.g. different database, schema, table, etc.). Then we’re reapplying events to this secondary read model. Once we’re caught up, we can switch queries to target the new read model and archive the old one.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d1cc091a4e13a66c1f1f5f6f305b79d5/a331c/2023-01-20-projection-07.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACEklEQVQozy2QW2/TQBCF/f+fkBAPCPGAkJB4QL0AQVSI0gIFqjRp3LS5OE4c33Y9s7Pj3fXaIRSEjJBGR/NypO98AduO3Q65Ifa1bbXx7f7PcBwevR6cnl+cff52/uX7cDzNS2TbSTQSKZNFLiVoF6B2yzidr7bhXZTkFbLLBV5cjg6OB0+ePjs4GhweD96dfBxd324LQN1kFc7Xy6wskF0g0L46fPvw0ePnL14u4gx0kwsqgUE3ir2q24ocmW5yF1+O50nBqxSjFHJppbIBmW6TyXE4W64zZF+Rq6gB3SA3yB50/wPZVVZfr+rhnZhENJxVYcxCuQC0Q/b9cuuJja4daKv6mquUk+RQN8CNYtf4bre/b3d7YqOoRnKBNi2ZjhTmcZgsr4t4WohMELPpvN8Z15Jp2e1ySdE6LyrNttO2Uz2jDYAskAOli3SVrBdpshKgUilTKU7CYhSjYofcTJbi0zA5v1qPFnh5K8IVC7SBVFYoJwHLZB7dXm2jGwEylRhtoweD0ZvhpkLYCFGAVuyN/+V399Hs6yb6AWQCSU4qB6hBpFAmSmYSIBFlKqtRXC0yhdrKns6S8ZPp7PTsS1YIzQxkAyTX+0RMljeL6Xgzn2RFXmJdKavYAVmJplJWYA26GYez9x/OMqGBXIUmmG5xnYqyIt2baFXdILdk/L9r/2fd9l5rb9u9//mbbIu6Z/kLPhNOV6dLAQ0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="projection" title="projection" src="/static/d1cc091a4e13a66c1f1f5f6f305b79d5/a331c/2023-01-20-projection-07.png" srcset="/static/d1cc091a4e13a66c1f1f5f6f305b79d5/36ca5/2023-01-20-projection-07.png 200w, /static/d1cc091a4e13a66c1f1f5f6f305b79d5/a3397/2023-01-20-projection-07.png 400w, /static/d1cc091a4e13a66c1f1f5f6f305b79d5/a331c/2023-01-20-projection-07.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="idempotency" style="position:relative;"><a href="#idempotency" aria-label="idempotency permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Idempotency</h2> <p>Some event-driven tooling promises to bring you the Holy Grail: exactly-once delivery. I explained in detail in <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">another article</a> that this is impossible.</p> <p><strong>The safe assumption is that we might get the same event more than once and run projection logic multiple times for the same event.</strong> Because of that, our projection handling needs to be idempotent. It’s a fancy word to say that no matter how many times we apply the same event, we’ll get the same result as we’d applied it only once.</p> <p>How to handle idempotency correctly?</p> <p><strong>The simplest case is <a href="https://martinfowler.com/articles/201701-event-driven.html">Event-Carried State Transfer</a>.</strong> It’s a fancy term for just pushing the latest state through events. Generally, it’s not the best practice, as it creates a coupling between event producer and consumer. Also, if we just send the state, we’re losing the context of the operation that caused the state change. However, it can be more than enough for cases like read model updates. Especially if our read model is just another representation of write model data.</p> <p><strong>Yet, it’s better to avoid having <a href="/en/state-obsession/">state obsession</a> and work on the event model design.</strong> For instance, if we have read model with the bank account balance <em>Payment Registered</em> event. If we put the transaction amount to the event payload, applying it more than once will result in the wrong balance calculation. If we additionally include the account balance after payment was registered, then updating the balance would become upsert, idempotent by default.</p> <p><strong>Of course, it’s a tradeoff, as we should keep events granular. <a href="/en/events_should_be_as_small_as_possible/">They should be as small as possible, but not smaller</a>.</strong> We should treat events as API. We should apply the same design practices as the others, so think if it should be API first or more tuned to consumer needs.</p> <p>If you’re still unsure of my reasoning, let’s look at that from a different angle. Which component should be responsible for doing balance calculation? In reality, it’s much more complex than just incrementing the value. We need to account for taxes, policies, previous balance etc. Do you really want to repeat this logic in read models or all the other consumers? The business logic should rather do such a calculation and propagate it further to subscribers. As always, pick your poison.</p> <p><strong>The next option is a more generic solution based on the event’s position in the log.</strong> We could pass the entity version in event metadata and store it in the read model during the update. If we assume that we’re getting events in the proper order, then we could compare the value during the update and ignore the change if it’s smaller than the value in the read model. I wrote about it longer in:</p> <ul> <li><a href="/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/">Dealing with Eventual Consistency and Idempotency in MongoDB projections</a></li> <li><a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">A simple trick for idempotency handling in the Elastic Search read model</a></li> </ul> <p><strong>You could also store events’ ids and ensure their uniqueness.</strong> This could be done as part of middleware around your event handler that tries to store the event id and stops processing if it was already handled. It may be part of your <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">inbox pattern deduplication</a> or stored in a dedicated table together with business results wrapped in the database transaction.</p> <p>If we decide to do that, we should consider keeping the handled events’ ids information for each projection. Why? To enable projection rebuilds and add new projections based on existing events.</p> <p><strong>My general recommendation is to work on the design and use the last handled position (checkpoints) to verify if the event was already handled.</strong> Other mechanisms may appear to be more sophisticated but also more fragile.</p> <h2 id="eventual-consistency" style="position:relative;"><a href="#eventual-consistency" aria-label="eventual consistency permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Eventual Consistency</h2> <p><strong>I often hear the phrase: <em>“business won’t let me to have stale data”</em>.</strong> That’s not surprising, as <a href="/en/bring_me_problems_not_solutions/">we’re not great at speaking with each other</a>.</p> <p>The first step to solving that is not to use technical jargon while talking with a business. We’re scaring them by using it and not helping to understand what we’re trying to say. Cross out eventual consistency from your dictionary before speaking with business. Ask them questions like:</p> <ul> <li><em>“What worse can happen if user won’t see this information immediately?”</em> or</li> <li><em>“Can we solve it differently, so we don’t need to show it at once?”</em></li> </ul> <p><strong>Show them the money and tell them how much development time and money it will cost for each solution.</strong> Don’t go too far into the implementation details. Try to build professional relationships in which we trust each other. So business knows about business, and we know the technical aspects.</p> <p><strong>Start also talking more with UX designers.</strong> Simple usability tricks may cut a lot of complexity from you. For instance, when adding a new product to the shopping cart, you might not redirect it to the shopping cart view. You may keep the user on the product page. It reduces the chance of not seeing the product in the shopping cart details and makes the user experience smoother. Typically users want to continue shopping and go to selected product details when they want to confirm and proceed to payment. Between those actions, our read model should become consistent.</p> <p>You can also use different techniques like push notifications from the server or simulate the synchronous flows in the asynchronous world by using long-polling. Read more in <a href="/en/long_polling_and_eventual_consistency/">Long-polling, how to make our async API synchronous</a>.</p> <p><strong>Still, the best advice is to work closely with the business and designers; this will save you a lot of accidental complexity.</strong> The real world has delays, and we should embrace that also in our technical design.</p> <h2 id="scaling-and-data-isolation" style="position:relative;"><a href="#scaling-and-data-isolation" aria-label="scaling and data isolation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Scaling and data isolation</h2> <p><em><strong>Will it scale? How to scale?</strong></em> Those are mantras we tell in all cases. Most of the time, we don’t know what we need and don’t know the exact metrics, but we’re already trying to solve imaginary scenarios. Don’t get me wrong; it’s essential to consider this before going to production. Yet, those considerations before knowing where we need to go are just pointless.</p> <p>Performance optimisation should be made based on the precise requirements (expressed as SLOs etc.) and verified with the benchmarks. Before trying to parallelise processing, we should ascertain if we need to optimise and if our current solution needs to scale more.</p> <p>I wrote about those considerations in <a href="/en/how_to_scale_projections_in_the_event_driven_systems/">How to scale projections in the event-driven systems?</a>.</p> <p><strong>In short, the foundational aspect of scaling projections is not the tech stack we use but the data partitioning we apply.</strong> To be able to parallelise, we need to isolate data. We could do it per module, customer, and region, but we can also go deeper. We can have a processor for each projection type (e.g. different for the user dashboard and shopping cart view). We can also go extreme and distribute the load on the row level. We should be good if projections are not competing for resources.</p> <p><strong>That also means we should not have multiple projections updating the same data.</strong> If we’re using a normalised relational database for our read models and have a shopping cart with product items as separate tables, then we should still have a single projection updating them. They’re conceptually grouped together, as you won’t have product items without a shopping cart, so keep a single writer updating it as a whole.</p> <p>Speaking about nested data. Check also <a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">How to create projections of events for nested object structures?</a></p> <h2 id="talk-is-cheap-show-me-the-code" style="position:relative;"><a href="#talk-is-cheap-show-me-the-code" aria-label="talk is cheap show me the code permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Talk is cheap; show me the code!</h2> <p>Let’s say that we have to implement the following projections:</p> <ol> <li>Detailed view of the shopping cart with:</li> </ol> <ul> <li>the total amount of products in the basket,</li> <li>total number of products</li> <li>list of products (e.g. if someone added the same product twice, then we should have one element with the sum).</li> </ul> <ol start="2"> <li>View with short information about pending shopping carts. It’s intended to be used as a list view for administration:</li> </ol> <ul> <li>the total amount of products in the basket,</li> <li>total number of products</li> <li>confirmed and cancelled shopping carts should be hidden.</li> </ul> <p>Let’s say we’re using some database that can store/upsert and delete the entities with provided id.</p> <p>We could add to it a helper that’d get the current state and store the updated result using C#:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DatabaseExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token generic-method"><span class="token function">GetAndStore</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">Database</span> database<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> T<span class="token punctuation">></span></span> update<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> item <span class="token operator">=</span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Get</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">T</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> database<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> <span class="token function">update</span><span class="token punctuation">(</span>item<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Having that, we could implement the first projection as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartDetails</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime<span class="token punctuation">?</span></span> ConfirmedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime<span class="token punctuation">?</span></span> CanceledAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> TotalPrice <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> TotalItemsCount <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartDetailsProjection</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Database</span> database<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">ShoppingCartDetailsProjection</span><span class="token punctuation">(</span><span class="token class-name">Database</span> database<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>database <span class="token operator">=</span> database<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ShoppingCartOpened<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartDetails</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">,</span> ClientId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> TotalPrice <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> TotalItemsCount <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAndStore</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> item <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> item<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> productItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> item<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> item<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> item<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PricedProductItem</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">+</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span>UnitPrice <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> item<span class="token punctuation">.</span>TotalPrice <span class="token operator">+=</span> productItem<span class="token punctuation">.</span>TotalAmount<span class="token punctuation">;</span> item<span class="token punctuation">.</span>TotalItemsCount <span class="token operator">+=</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token keyword">return</span> item<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ProductItemRemovedFromShoppingCart<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAndStore</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> item <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> item<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> productItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">-</span> productItem<span class="token punctuation">.</span>Quantity <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token comment">// You may consider throwing exception here, depending on your strategy</span> <span class="token keyword">return</span> item<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">-</span> productItem<span class="token punctuation">.</span>Quantity <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> item<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> item<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> item<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PricedProductItem</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">-</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span>UnitPrice <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> item<span class="token punctuation">.</span>TotalPrice <span class="token operator">-=</span> productItem<span class="token punctuation">.</span>TotalAmount<span class="token punctuation">;</span> item<span class="token punctuation">.</span>TotalItemsCount <span class="token operator">-=</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token keyword">return</span> item<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ShoppingCartConfirmed<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAndStore</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> item <span class="token operator">=></span> <span class="token punctuation">{</span> item<span class="token punctuation">.</span>Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">;</span> item<span class="token punctuation">.</span>ConfirmedAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">;</span> <span class="token keyword">return</span> item<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ShoppingCartCanceled<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAndStore</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> item <span class="token operator">=></span> <span class="token punctuation">{</span> item<span class="token punctuation">.</span>Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Canceled<span class="token punctuation">;</span> item<span class="token punctuation">.</span>CanceledAt <span class="token operator">=</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">;</span> <span class="token keyword">return</span> item<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And the second projection as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartShortInfo</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> TotalPrice <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> TotalItemsCount <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartShortInfoProjection</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Database</span> database<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">ShoppingCartShortInfoProjection</span><span class="token punctuation">(</span><span class="token class-name">Database</span> database<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>database <span class="token operator">=</span> database<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ShoppingCartOpened<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartShortInfo</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> ClientId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> TotalPrice <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> TotalItemsCount <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAndStore</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartShortInfo<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> item <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> item<span class="token punctuation">.</span>TotalPrice <span class="token operator">+=</span> productItem<span class="token punctuation">.</span>TotalAmount<span class="token punctuation">;</span> item<span class="token punctuation">.</span>TotalItemsCount <span class="token operator">+=</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token keyword">return</span> item<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ProductItemRemovedFromShoppingCart<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAndStore</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartShortInfo<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> item <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> item<span class="token punctuation">.</span>TotalPrice <span class="token operator">-=</span> productItem<span class="token punctuation">.</span>TotalAmount<span class="token punctuation">;</span> item<span class="token punctuation">.</span>TotalItemsCount <span class="token operator">-=</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token keyword">return</span> item<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ShoppingCartConfirmed<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Delete</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartShortInfo<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">EventEnvelope<span class="token punctuation">&lt;</span>ShoppingCartCanceled<span class="token punctuation">></span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Delete</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartShortInfo<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>As you see, projections use using simple handler that takes the upcoming events, loads the current state, runs the projection logic and stores the result.</strong> So, doing left-fold.</p> <p>That’s also how <a href="https://martendb.io/events/projections/">Marten projections</a> are working. Yet, they’re doing much more internally. So batching, parallelising etc.</p> <p>Of course, as explained earlier, you don’t need to do left fold; you could even just run an SQL statement, e.g.:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UserDashboardProjection</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Projection</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">NpgsqlConnection</span> databaseConnection<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">UserDashboardProjection</span><span class="token punctuation">(</span><span class="token class-name">NpgsqlConnection</span> databaseConnection<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>databaseConnection <span class="token operator">=</span> databaseConnection<span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserCreated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>Apply<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserNameUpdated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>Apply<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">Projects</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>OrderCreated<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>Apply<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">UserCreated</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> databaseConnection<span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span> <span class="token string">@"INSERT INTO UserDashboards (Id, UserName, OrdersCount, TotalAmount) VALUES (@UserId, @UserName, 0, 0)"</span><span class="token punctuation">,</span> @<span class="token keyword">event</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">UserNameUpdated</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> databaseConnection<span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span> <span class="token string">@"UPDATE UserDashboards SET UserName = @UserName WHERE Id = @UserId"</span><span class="token punctuation">,</span> @<span class="token keyword">event</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">OrderCreated</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> databaseConnection<span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span> <span class="token string">@"UPDATE UserDashboards SET OrdersCount = OrdersCount + 1, TotalAmount = TotalAmount + @Amount WHERE Id = @UserId"</span><span class="token punctuation">,</span> @<span class="token keyword">event</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Or do batch processing.</p> <p>Projections should be as close as possible to end storage to be efficient. Create abstractions when needed, but beware to avoid ending up with the lowest common denominator.</p> <h2 id="summing-up" style="position:relative;"><a href="#summing-up" aria-label="summing up permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summing up</h2> <p><strong>Projections are powerful mechanism.</strong> In a nutshell, they’re <em>just</em> transformations of information we got from events into other data. We can look at the past and analyse the data, finding even new business models. Thanks to that, we can get business insights and view data from different perspectives.</p> <p>To implement projections efficiently and benefit fully from their superpowers, we need to take a lot into consideration.</p> <p>I hope this article is a decent starting point for you to know how to deal with projections and what to watch for. I showed foundational building blocks from a big-picture view, challenges and potential solutions.</p> <p>There’s plenty more to discuss at each stop on this city bus tour. Feel free to post your questions and concerns in the comments!</p> <p><strong><a href="/en/training/">Consider my training</a> as a way to delve more into the specific bits relevant to your project needs.</strong> A real workshop is the best way to learn, discuss and get hands-on experience.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to validate business logic]]>https://event-driven.io/en/how_to_validate_business_logic/https://event-driven.io/en/how_to_validate_business_logic/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/81cb81f0a2ae8318986c32161541d119/c60e9/2023-01-15-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMBAgX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAAL/2gAMAwEAAhADEAAAAVXyI0AsL//EABkQAQADAQEAAAAAAAAAAAAAAAIAAREDEP/aAAgBAQABBQLldR0cWbTVRNLz/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQIBAT8Bp//EABgQAQEAAwAAAAAAAAAAAAAAAAAhARAx/9oACAEBAAY/AlR1c6//xAAbEAEBAAEFAAAAAAAAAAAAAAABAEEQESExkf/aAAgBAQABPyHJ9jtXM85IXYnY7p//2gAMAwEAAgADAAAAEEgf/8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8QiP/EABcRAQADAAAAAAAAAAAAAAAAAAABESH/2gAIAQIBAT8QwuX/xAAcEAACAgIDAAAAAAAAAAAAAAABEQAhMXFBUZH/2gAIAQEAAT8QMBSyKwW5eEjPAhpv2FK4hoRcpFAYHkU//9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/81cb81f0a2ae8318986c32161541d119/c60e9/2023-01-15-cover.jpg" srcset="/static/81cb81f0a2ae8318986c32161541d119/37402/2023-01-15-cover.jpg 200w, /static/81cb81f0a2ae8318986c32161541d119/4cda9/2023-01-15-cover.jpg 400w, /static/81cb81f0a2ae8318986c32161541d119/c60e9/2023-01-15-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Fox Mulder got advice: “trust no one”.</strong></p> <p>I’m claiming that each software developer should define their level of paranoia.</p> <p><strong>The thing that we should never trust is the outside world.</strong> At least if we’re creating the public API. That includes web requests, messages from the queue, and maybe even data we have in the database.</p> <p>Can we at least trust ourselves? We’ll get to that.</p> <p><strong>Let’s discuss the classical 3-tiered architecture where we have frontend communication with Web API that’s interacting with the database.</strong> Let’s look at where the data processing pipeline can go wrong:</p> <ol> <li>There was no validation on the frontend, or it didn’t check all conditions. We cannot assume that we will be flawless and can standardise everything. The more elements we have in the development pipeline, the greater the chance that our colleagues or we will overlook them.</li> <li>The API has changed its business logic, and the frontend doesn’t know about it yet.</li> <li>Frontend validation cannot verify some conditions. For instance, whether the product is still in stock or if the user email is unique. Querying the backend to check these conditions is not enough. Why? I explained that in <a href="/en/tell_dont_ask_how_to_keep_an_eye_on_boiling_milk/">Tell, don’t ask! Or, how to keep an eye on boiling milk</a>.</li> <li>Our API is public. Most of the APIs use text-based representations for messages they get. That means anyone can handcraft JSON, XML or plain text and send any data. That means, e.g. empty request, wrong message format (XML instead of JSON), not providing required data, invalid data format (string instead of a number, array instead of single value, etc.).</li> <li>Someone may deliberately perform a malicious action, e.g. sending invalid requests to break our system or steal data by doing <a href="https://en.wikipedia.org/wiki/Data_scraping">data scraping</a>. Anyone can poke our API with a stick, trying to find holes and extract data by analysing the response.</li> </ol> <p>Basically, anything can happen. If we don’t <a href="/en/form_a_wall/">form a wall</a>, we might get into real trouble.</p> <p><strong>The same can be true of a database.</strong> And that’s even dangerous, as we believe that it’s our data and we have full control. That can catch us off-guard. Both data structure and its meaning evolve with the software’s lifetime. Some fields become required, some become obsolete, and some are dropped. We won’t be able from the beginning to provide the end solution. Not speaking about the scenario where we have a big ball of mud, and we’re integrating multiple modules/services through the database.</p> <p>Embracing that the outside world can be evil or just different than we expect is a basis of Ports &#x26; Adapters, so <a href="https://herbertograca.com/2017/11/16/explicit-architecture-01-ddd-hexagonal-onion-clean-cqrs-how-i-put-it-all-together/">Hexagonal Architecture</a>.</p> <p>Even if you didn’t watch X-Files, you should start agreeing with Fox Mulder’s conclusion.</p> <p>If we should trust no one, then how do we live? We have to trust someone. We can try to trust our own code. Aka, famous last words. Be careful with that. Control is the foundation of trust.</p> <p>My general flow is as follows:</p> <h2 id="1-i-make-api-request-classes-as-plain-objects-using-primitives" style="position:relative;"><a href="#1-i-make-api-request-classes-as-plain-objects-using-primitives" aria-label="1 i make api request classes as plain objects using primitives permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>1. I make API request classes as plain objects using primitives.</h2> <p>I assume that I can get anything, null, invalid format, everything can be wrong. I try to <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">parse, don’t validate</a>. The task of such a class is to translate the data from the request to the instance of the class. I usually keep such a class directly in an API project. It may look as follows in C#</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AddProductRequest</span><span class="token punctuation">(</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> ProductId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> Quantity <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h2 id="2-having-parsed-the-request-im-mapping-it-to-the-real-contract-eg-command-or-query" style="position:relative;"><a href="#2-having-parsed-the-request-im-mapping-it-to-the-real-contract-eg-command-or-query" aria-label="2 having parsed the request im mapping it to the real contract eg command or query permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>2. Having parsed the request, I’m mapping it to the real contract (e.g. command or query).</h2> <p>This contract already comes from the domain module. This is where the element of trust comes in. I can trust this code, as I’m instantiating in my code, plus I’m also responsible for defining how to do it. I’m usually creating a static factory method and am not shy to use <a href="/en/immutable_value_objects/">value objects</a> here to enforce semantic validation. The mapping code could look like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> AddProduct<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> ProductItem<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span> request<span class="token punctuation">?.</span>ProductItem<span class="token punctuation">?.</span>ProductId<span class="token punctuation">,</span> request<span class="token punctuation">?.</span>ProductItem<span class="token punctuation">?.</span>Quantity <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And types definition:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AddProduct</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> ProductItem <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">AddProduct</span> <span class="token function">From</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> cartId<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cartId <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>cartId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">AddProduct</span><span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItem</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ProductId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Quantity <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ProductItem</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> productId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> quantity<span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductId <span class="token operator">=</span> productId<span class="token punctuation">;</span> Quantity <span class="token operator">=</span> quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItem</span> <span class="token function">From</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> productId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> quantity<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>productId<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> quantity <span class="token keyword">switch</span> <span class="token punctuation">{</span> <span class="token keyword">null</span> <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>quantity<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token operator">&lt;=</span> <span class="token number">0</span> <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>quantity<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"Quantity has to be a positive number"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItem</span><span class="token punctuation">(</span>productId<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> quantity<span class="token punctuation">.</span>Value<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p>As with everything, this pattern also has its name <a href="https://wiki.haskell.org/Smart_constructors">Smart Constructor</a>. It comes from functional programming, but as you see, even in the imperative world, it makes a lot of sense.</p> <p><strong>After creating a command or query instance, we know it’s correct.</strong> By <em>correct</em>, I mean that it fulfils the basic assumptions like: all required fields have assigned values, fields have correct types, validations like product item quantity is positive, etc. It is crucial not to do sophisticated domain logic validation here but semantic one.</p> <p>You may also notice that I’ve used <a href="/en/notes_about_csharp_records_and_nullable_reference_types/">records types</a>. That means that instances of these classes will be immutable. Most languages nowadays allow defining such structures, e.g. <a href="https://openjdk.org/jeps/395">Java also has records</a>, TypesScript <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#readonlytype">readonly types</a> and functional languages have that by default. Why is it so important?</p> <p><strong>Thanks to immutability, we’re getting even better trust with our objects.</strong> We know no one will change them by doing accidental cowboy updates. We can pass them as parameters; they will always be as we created them.</p> <p><strong>That also reduces the amount of duplicated validation. We just do it once.</strong> Of course, we should unit test our smart constructor, but we don’t need to repeat it. We’re getting fewer tests as the compiler will do a lot of work for us.</p> <p>Check also more in my other article on <a href="/en/explicit_validation_in_csharp_just_got_simpler/">how to make explicit validation simpler with recent C# and .NET improvements</a>.</p> <p>You can also consider doing <a href="/en/explicit_events_serialisation_in_event_sourcing/">explicit deserialisation</a>.</p> <h2 id="3-proper-domain-validation-should-be-done-in-business-logic" style="position:relative;"><a href="#3-proper-domain-validation-should-be-done-in-business-logic" aria-label="3 proper domain validation should be done in business logic permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>3. Proper domain validation should be done in business logic.</h2> <p>That’s why I like CQRS. Thanks to CQRS, we know that a specific handler will execute the command. Business logic will be routed to a particular function or aggregate method. If we are to change the rule, we don’t have to look at the whole code with unsteady eyes. For example, it is worth validating in the command whether the quantity is positive, but all the others, like checking if there are enough product items in the cart should be made in the business logic. Example:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">ShoppingCartStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddProduct</span><span class="token punctuation">(</span> <span class="token class-name">IProductPriceCalculator</span> productPriceCalculator<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Adding product for the cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> pricedProductItem <span class="token operator">=</span> productPriceCalculator<span class="token punctuation">.</span><span class="token function">Calculate</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">MergeWith</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span> <span class="token class-name">PricedProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Removing product from the cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>UnitPrice</span><span class="token punctuation">}</span></span><span class="token string">' was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>existingProductItem<span class="token punctuation">.</span><span class="token function">HasEnough</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">existingProductItem<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem<span class="token punctuation">.</span><span class="token function">HasTheSameQuantity</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">Subtract</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>The other story is whether to throw exceptions in business logic.</strong> That’s highly dependent on the technology you use, team experience and preferences. I’ll expand on that in the dedicated post, but for now, I recommend following the conventions and capabilities of your coding environment.</p> <p>If you’re coding in functional programming, Go, or Rust, you won’t throw exceptions too much. You’ll likely use exceptions if you’re into C# or Java. Why? I wrote about it longer in <a href="/en/union_types_in_csharp/">Union types in C#</a>. If you’re into swiss-scissor language like TypeScript, you might do one or another.</p> <p>The most important thing is to refrain from fighting the language and local conventions because code created that way will be hard to maintain and constantly fight with the tooling. It can be beneficial in some scenarios, but I’d try to avoid it as a general approach.</p> <p>Also, aggregates are one of many ways to handle business logic. Read more in <a href="/en/how_to_effectively_compose_your_business_logic/">How to effectively compose your business logic</a> and <a href="/en/slim_your_entities_with_event_sourcing/">Slim your aggregates with Event Sourcing!</a>.</p> <h2 id="summing-up" style="position:relative;"><a href="#summing-up" aria-label="summing up permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summing up</h2> <p><strong>Being more explicit may seem a bit redundant at first, but thanks to that:</strong></p> <ul> <li>we increase trust and the security of our code,</li> <li>we make changes in the domain code independent of changes in the API,</li> <li>we can cut off edge scenarios one by one: deserialisation, semantic validation of types, and business validation.</li> <li>it is easier to know what, where and how to change, thanks to increasing maintainability and cognitive load.</li> <li>we reduce the number of tests needed.</li> </ul> <p>Of course, all of that requires consistency, but once we build it and work carefully on our types, it’ll get easier with each new one.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Let's build event store in one hour!]]>https://event-driven.io/en/lets_build_event_store_in_one_hour/https://event-driven.io/en/lets_build_event_store_in_one_hour/<p><strong>Last year, I completed two items from my speaker bucket list <a href="https://ndcoslo.com/speakers/oskar-dudycz">NDC Oslo</a> and <a href="https://2022.dddeurope.com/program/keep-your-streams-short!-or-how-to-model-event-sourced-systems-efficiently">Domain-Driven Design Europe</a>.</strong> I’m proud and happy, as those conferences were a huge inspirations for me.</p> <p><strong>At NDC Oslo, I showed one of my favourite talks: “Let’s build event store in one hour!“.</strong> Why do I like it so much? It’s a fun mixture of live coding and theory explanation. At least for me.</p> <p>It’s technically hard talk to deliver as I need to coordinate speaking, live coding and not boring people with technical details. It’s kinda speed run for me. But when precisely delivered, I think that’s fun talk to watch. Check it on your own!</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/gaoZdtQSOTo?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>This talk does not intend to persuade you to write your own event store. There are already mature solutions like <a href="https://martendb.io/">Marten</a>, <a href="https://www.eventstore.com/">EventStoreDB</a>, <a href="https://www.axoniq.io/">Axon</a>.</strong></p> <p>The intention is to <a href="/pl/dive_a_bit_deeper_look_a_bit_wider/">dive a bit deeper</a>, showing that Event Sourcing and event stores can be treated as boring technology in the good sense. It’s about showing that it’s not a hyped buzzword but a tool that can bring real benefits and give you the guarantees you need.</p> <p>I like to see how people’s eyes get bigger when they see the SQL stored procedure. I also like when they’re surprised that I’m not using any messaging tooling or regular database transactions. Most people also don’t expect that read models can also be updated synchronously without having eventual consistency.</p> <p><strong>So it’s more about myths busting and <a href="/pl/the_magic_is_that_there_is_no_magic/">explaining Event Sourcing pattern in a nutshell</a>.</strong></p> <p>What are the requirements, then? In my opinion, at least:</p> <ul> <li>appending event at the end of the stream,</li> <li>reading all events from the stream,</li> <li>a guarantee of the ordering within the stream,</li> <li>being able to read your writes,</li> <li>strong-consistent, atomic writes and optimistic concurrency.</li> </ul> <p>The 2nd tier of event stores features (so great to have but not must-haves):</p> <ul> <li>being able to subscribe to notifications about newly appended events (best if it’s push-based)</li> <li>global ordering,</li> <li>built-in projections,</li> <li>streams archiving.</li> </ul> <p>Check more on those considerations in the:</p> <ul> <li><a href="/pl/relational_databases_are_event_stores/">What if I told you that Relational Databases are in fact Event Stores?</a></li> <li><a href="https://cqrs.wordpress.com/documents/building-event-storage/">Greg Young - Building an Event Storage</a></li> <li><a href="https://www.eventstore.com/blog/requirements-for-the-storage-of-events">Yves Lorphelin - Requirements for the storage of events</a>,</li> <li><a href="https://medium.com/itnext/essential-features-of-an-event-store-for-event-sourcing-13e61ca4d066">Anton Stöckl - Essential features of an Event Store for Event Sourcing</a></li> <li><a href="https://www.youtube.com/watch?v=YUjO1wM0PZM">Greg Young - How an EventStore actually works</a></li> <li><a href="https://softwaremill.com/implementing-event-sourcing-using-a-relational-database/">Adam Warski - Implementing event sourcing using a relational database</a></li> </ul> <p><strong>I also have something special for you, a self-paced kit on building event store on top of Relational Database using Postgres as an example.</strong> It starts with the tables set up and goes through appending events, aggregations, handling business logic, time travelling, projections, and snapshots.</p> <p>I think that’s a fun way to learn Event Sourcing, check versions for:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Workshops/BuildYourOwnEventStore">C#</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/pull/36">Java</a>.</li> </ul> <p>Once you’re done, please don’t use it on production; rather, use some mature solutions. Maintaining it won’t be sustainable if it’s not your core business.</p> <p><strong>Event stores are databases. Just like you won’t say <em>“hey, let’s build the relational database for our project”</em>, you should not do that with an event store.</strong></p> <p>Still, it’s a lot of fun, nothing too time-consuming to build a simple one and an excellent exercise in understanding how Event Sourcing works.</p> <p>Not convinced? Read then why <a href="/en/event_stores_are_key_value_stores">you can think about event stores as key-value stores, and why that matters</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Don't be like Ebenezer Scrooge. A few words about workaholism]]>https://event-driven.io/en/a_few_words_about_workaholism/https://event-driven.io/en/a_few_words_about_workaholism/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c1b6dc893aad7424294cf0714352b329/c60e9/2022-12-29-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABAACBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAee8mwsuP//EABsQAAMAAgMAAAAAAAAAAAAAAAECAwARBBIz/9oACAEBAAEFAogbSM2QzOS8l5LbavY//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAHRAAAgEEAwAAAAAAAAAAAAAAAAECEBEhYTJBUf/aAAgBAQAGPwL3RJt51Sb7VjKucUf/xAAaEAADAQADAAAAAAAAAAAAAAAAAREhMUFR/9oACAEBAAE/IWNqpnoaegIsaK46GIJBl3OTUmf/2gAMAwEAAgADAAAAEBPP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHxABAAICAQUBAAAAAAAAAAAAAQARIUExUWFxgaHw/9oACAEBAAE/EFK1/Hy8/fUZmRq3IGMJ1i0KrUqCJNpnXeNDcWwIvdH64lUdUAM//9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/c1b6dc893aad7424294cf0714352b329/c60e9/2022-12-29-cover.jpg" srcset="/static/c1b6dc893aad7424294cf0714352b329/37402/2022-12-29-cover.jpg 200w, /static/c1b6dc893aad7424294cf0714352b329/4cda9/2022-12-29-cover.jpg 400w, /static/c1b6dc893aad7424294cf0714352b329/c60e9/2022-12-29-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>There is a cult of workaholism in our industry.</strong> Just like the guys from the gym brag about how much they pulled on the chest or did in the deadlift, we brag about how many overtime hours we worked. Or how long did we work to fix the production bug. All those jokes:</p> <ul> <li><em>“5 PM and you are going home?”</em></li> <li><em>“Yeah, I’m on vacation today.”</em></li> </ul> <p>Ha. Ha.</p> <p><strong>I’m also struggling with that. I have strong workaholism tendencies.</strong> I also bragged about overtime. I even came up with my lame joke <em>“I don’t work too much; I just do my overdued overtime.”</em> Probably to this day, among my colleagues, I may have the opinion of robocop, which works non-stop. It’s justified as I had a span of ~2 years where I worked an average of 220 hours per month.</p> <p>That’s something to be proud of.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 295px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d8d1907194da6d5610cdeb1f5f96080e/2361d/2022-12-29-not.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 115.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAXABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAAAAME/8QAFwEAAwEAAAAAAAAAAAAAAAAAAAIDBP/aAAwDAQACEAMQAAABpHdnm9GhNoVNMc4Y/8QAGxABAQADAAMAAAAAAAAAAAAAAQIDERIAEyH/2gAIAQEAAQUCvGsRExfp35VjfKomsXItfL01/8QAGBEAAgMAAAAAAAAAAAAAAAAAAAEQERL/2gAIAQMBAT8B0i5//8QAGREAAQUAAAAAAAAAAAAAAAAAAAEQERJh/9oACAECAQE/AaqRj//EAB4QAAIBAwUAAAAAAAAAAAAAAAABIRAiMQIRElFx/9oACAEBAAY/AuKgtdvpAtKzkl0e5HVP/8QAHBABAAMAAwEBAAAAAAAAAAAAAQARITFxoWHh/9oACAEBAAE/IbwWvyP7c6NtitVr7Gj2PSD1RkEA9gp2oQB4vhnX7LR4n//aAAwDAQACAAMAAAAQ5/fA/8QAGBEAAgMAAAAAAAAAAAAAAAAAAAEQIXH/2gAIAQMBAT8QaHZqf//EABgRAQADAQAAAAAAAAAAAAAAAAEAECER/9oACAECAQE/EBDIrsJtf//EAB0QAQEAAwEBAAMAAAAAAAAAAAERACExQVGBkbH/2gAIAQEAAT8Q3RmyMX0XzVyplyI2z5zX7xyrZqgcv1aQ153k12ZJ37/cdEQAqnmPoMRDkWONXg/kg58x22l0c65//9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="not" title="not" src="/static/d8d1907194da6d5610cdeb1f5f96080e/2361d/2022-12-29-not.jpg" srcset="/static/d8d1907194da6d5610cdeb1f5f96080e/37402/2022-12-29-not.jpg 200w, /static/d8d1907194da6d5610cdeb1f5f96080e/2361d/2022-12-29-not.jpg 295w" sizes="(max-width: 295px) 100vw, 295px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>NOT!</strong></p> <p>I felt uplifted for a long time when I heard, <em>“damn, Oskar is a stunner, he works so hard”</em>. Or by the feeling of saving the project once again. It was also a boast of being so multitasking. Seven projects at once? No problem. Two calls at a time. Sure, why not.</p> <p>The reality is that all a myth. Multitasking is inefficient. You can’t do many things at once for free. None of these things is done as satisfactorily as possible if you focus only on them. Saving the project and taking on the most challenging stuff is blocking others from learning and proving themselves. It’s a dead end. Especially being a leader, it is worth restraining your workaholic tendencies. Even if we never ask anyone to work overtime (e.g. I never did), and we don’t undervalue those who work <em>only</em> from 9 till 5, we still put pressure on them. That’s dangerous, especially for juniors. Keep this in mind. <a href="/en/it_doesnt_have_to_be_toxic_at_work/">It doesn’t have to be toxic at work</a>.</p> <p><strong>We’re lucky that we have a job that is also our passion.</strong> Plus a well-paying hobby. Yet, it cuts both ways and can be a curse.</p> <p>Are there deadlines that you can’t move? There are no. At least at work. I assure you there are no deadlines that cannot be moved. I haven’t seen such in over 15 years of my career.</p> <p>I will tell you one more secret. We are not unique. The worn-out slogan that there are no irreplaceable people is true. Seriously.</p> <p><strong>You, too, can be replaced.</strong></p> <p><strong>At work.</strong></p> <p><strong>There is an area where we cannot be replaced.</strong></p> <p><strong>At home.</strong></p> <p>So let’s remember (I’m writing to you and myself) to focus less on work issues, especially in our free time. Deadlines will come and go. Let’s focus on here and now. Let’s focus on the people, e.g., those around the table during your next dinner. You never know if you may meet in such a configuration again.</p> <p>Let’s not be like Ebenezer Scrooge.</p> <p>Let’s laugh at the dad’s jokes told for the hundredth time; they’re still funnier than our overtime jokes.</p> <p>We’ll miss grandma’s food that we hated some time ago. We’d like to hear that boring uncle’s story again at some point.</p> <p><strong>We’ll likely miss them more than ephemeral deadlines at work or overtimes we did.</strong></p> <p>Read also:</p> <ul> <li><a href="/en/it_doesnt_have_to_be_toxic_at_work/">It doesn’t have to be toxic at work</a></li> <li><a href="/en/keeping_overachieving_freak_on_a_leash">Keeping our overachieving freak on a leash</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Explicit events serialisation in Event Sourcing]]>https://event-driven.io/en/explicit_events_serialisation_in_event_sourcing/https://event-driven.io/en/explicit_events_serialisation_in_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/203e334658240b0cc3fa1c591db510f9/8299d/2022-12-23-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAIDBAX/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/9oADAMBAAIQAxAAAAHHmpMIAf/EABsQAAMAAgMAAAAAAAAAAAAAAAABAgMEERIT/9oACAEBAAEFAsWL0L1qmRNo7Pg//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAGhAAAgIDAAAAAAAAAAAAAAAAAREAECExYf/aAAgBAQAGPwI8hL1WInX/xAAZEAEBAAMBAAAAAAAAAAAAAAARAQAQITH/2gAIAQEAAT8hpYpgEqHXqjLdONf/2gAMAwEAAgADAAAAECAP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxABAQADAAMAAAAAAAAAAAAAAREAEEFxgfD/2gAIAQEAAT8QLTgLe1yIs6PvOmFRJGYmQvnu6//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/203e334658240b0cc3fa1c591db510f9/c60e9/2022-12-23-cover.jpg" srcset="/static/203e334658240b0cc3fa1c591db510f9/37402/2022-12-23-cover.jpg 200w, /static/203e334658240b0cc3fa1c591db510f9/4cda9/2022-12-23-cover.jpg 400w, /static/203e334658240b0cc3fa1c591db510f9/c60e9/2022-12-23-cover.jpg 800w, /static/203e334658240b0cc3fa1c591db510f9/6c738/2022-12-23-cover.jpg 1200w, /static/203e334658240b0cc3fa1c591db510f9/56dca/2022-12-23-cover.jpg 1600w, /static/203e334658240b0cc3fa1c591db510f9/8299d/2022-12-23-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Events serialisation is an intriguing topic.</strong> On the one hand, it’s part of the campfire spooky tales, so (in)famous events versioning. On the other, we’re just taking event type name and event data and serialising it back and forth to string or binary data. That cannot be that hard, aye?</p> <p><strong>The truth (as always) lies somewhere in the middle.</strong> Indeed, it’s not as hard as envisioned, but there’s also a reason I am writing a 5th blog article about that, right?</p> <p>I wrote already about <a href="/en/simple_events_versioning_patterns/">simple patterns for events schema versioning</a>. I explained <a href="/en/how_to_do_event_versioning/">why the best way of doing event versioning is not having the need for that</a>. I also presented <a href="/en/event_versioning_with_marten/">how to do versioning in Marten</a>. In the last article, I showed <a href="/en/how_to_map_event_type_by_convention/">mapping event type by convention</a>. I’ll show you how and when it may be worth serialising explicit events this time.</p> <p><strong>Conventional mapping can take you far if you have a set of basic conventions and serialiser that can do a lot.</strong> In many languages, like C# or Java, serialisers can go pretty wild and do advanced mappings. That’s quite powerful as long as you obey the rules. Creator rules. That’s fine until you’ll try to do something a bit unusual.</p> <p><strong>What can unusual mean in this context?</strong> For instance: building a complex type system instead of just using primitives. Or: connecting evolved type structure to the old event payload.</p> <p><strong>Let’s discuss that in the specific scenario.</strong> I showed you already that <a href="/en/using_strongly_typed_ids_with_marten/">strongly-typed ids might play poorly with a lot of tooling</a>. Still, if you put enough effort or use the proper patterns, they’re the simplest example of <a href="/en/immutable_value_objects/">value objects</a> and can make your life easier. Once you build your type system, you get predictability as the compiler will know better, fewer unit tests and a much more expressive and explicit codebase. All of that makes your code much closer to the business language, a <a href="/en/bring_me_problems_not_solutions/">base for shared understanding</a> and better matching the expected results.</p> <p>Let’s have a look at our events definition. They’re facts that can be observed during the Shopping Cart lifetime:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">ClientId</span> ClientId <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">LocalDateTime</span> ConfirmedAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span> <span class="token class-name">ShoppingCartId</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">LocalDateTime</span> CanceledAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As you noticed, I’m using strongly typed ids (<em>ShoppingCartId</em> and <em>ClientId</em>), custom types like <em>LocalDateTime</em> and value objects for priced product items.</p> <p>They can be defined, for instance, as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ClientId</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token function">ClientId</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> <span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token class-name">ClientId</span> Unknown <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span>Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ClientId</span> <span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ClientId</span> <span class="token function">Parse</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>Guid<span class="token punctuation">.</span><span class="token function">TryParse</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> guidValue<span class="token punctuation">)</span> <span class="token operator">||</span> guidValue <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ClientId</span><span class="token punctuation">(</span>guidValue<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Amount</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">,</span> <span class="token class-name">IComparable<span class="token punctuation">&lt;</span>Amount<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token function">Amount</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsPositive <span class="token operator">=></span> Value <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> <span class="token function">CompareTo</span><span class="token punctuation">(</span><span class="token class-name">Amount<span class="token punctuation">?</span></span> other<span class="token punctuation">)</span> <span class="token operator">=></span> Value<span class="token punctuation">.</span><span class="token function">CompareTo</span><span class="token punctuation">(</span>other<span class="token punctuation">?.</span>Value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Amount</span> <span class="token function">Parse</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">Currency</span> <span class="token punctuation">{</span> USD<span class="token punctuation">,</span> EUR<span class="token punctuation">,</span> PLN <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Money</span><span class="token punctuation">(</span> <span class="token class-name">Amount</span> Amount<span class="token punctuation">,</span> <span class="token class-name">Currency</span> Currency <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The others are defined accordingly, creating a primary type with validations and not allowing incorrect values. <em>StronglyTypedValue</em> is the class known from the <a href="/en/using_strongly_typed_ids_with_marten/">previous article</a> responsible for implementing the equality boilerplate etc. So as you see, nothing spectacular. In C#, some boilerplate is needed; in many languages (<a href="/en/writing_and_testing_business_logic_in_fsharp/">e.g. F#</a>), this could look even simpler.</p> <p><strong>This approach is flexible, as we can model our types case by case following the business specification. Yet, that’s also the weakness if we’d like to use the convention. Customisation and flexibility by convention don’t follow conventions.</strong></p> <p>The solution for that is explicit serialisation. We won’t be trying to fight the tooling but telling it explicitly how to do the work.</p> <h2 id="how-to-handle-serialisation-like-that" style="position:relative;"><a href="#how-to-handle-serialisation-like-that" aria-label="how to handle serialisation like that permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>How to handle serialisation like that?</h2> <p>Let’s define the Serde (serialisation/deserialisation) class that will handle JSON serialisation using System.Text.Json serialiser. The serialisation method could look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartEventsSerde</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token punctuation">(</span><span class="token keyword">string</span> EventType<span class="token punctuation">,</span> JsonObject Data<span class="token punctuation">)</span></span> <span class="token function">Serialize</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartEvent</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> @<span class="token keyword">event</span> <span class="token keyword">switch</span> <span class="token punctuation">{</span> <span class="token return-type class-name">ShoppingCartOpened</span> e <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token string">"shopping_cart_opened"</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Object</span><span class="token punctuation">(</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"clientId"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ClientId<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">ProductItemAddedToShoppingCart</span> e <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token string">"product_item_added_to_shopping_cart"</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Object</span><span class="token punctuation">(</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"productItem"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">ProductItemRemovedFromShoppingCart</span> e <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token string">"product_item_removed_from_shopping_cart"</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Object</span><span class="token punctuation">(</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"productItem"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">ShoppingCartConfirmed</span> e <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token string">"shopping_cart_confirmed"</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Object</span><span class="token punctuation">(</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"confirmedAt"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ConfirmedAt<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">ShoppingCartCanceled</span> e <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token string">"shopping_cart_canceled"</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Object</span><span class="token punctuation">(</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Json<span class="token punctuation">.</span><span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"canceledAt"</span><span class="token punctuation">,</span> e<span class="token punctuation">.</span>CanceledAt<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>We’re taking the <em>ShoppingCartEvent</em> and doing a switch based on the exact event type. Knowing this, we can do a specific serialisation of the event data.</strong> We have full flexibility in defining the event structure. We can flatten the structure next to it and set custom property names. This freedom is especially valuable for handling <a href="/en/simple_events_versioning_patterns/">event schema evolution</a>. Thanks to that, we can handle compatibility issues explicitly. As a serialisation result, we’re returning both serialised data and a mapped event type name. That’s needed to deserialise it, as we’ll see later.</p> <p><strong>The code also doesn’t look that scary besides the <em>stringly-typed</em> property names.</strong> It is dangerous, as we can make a copy-paste mistake. As always, with great power comes great responsibility. Of course, we could make those strings const values or map them from the type names, but that’s also not ideal.</p> <p>Let’s say we made a constant value with the shopping cart id property name.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">const</span> shoppingCartIdPropertyName <span class="token operator">=</span> <span class="token string">"shoppingCartId"</span><span class="token punctuation">;</span></code></pre></div> <p>Now we’re reusing it in the multiple serialisation scenarios. If we decided to rename the value of the property for one scenario, e.g. to:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">const</span> shoppingCartIdPropertyName <span class="token operator">=</span> <span class="token string">"cartId"</span><span class="token punctuation">;</span></code></pre></div> <p>Then we could forget that it’s also used in other places, making a ripple effect of breaking changes.</p> <p>So as always, pick your poison. There are no best solutions, just better or worse in a specific context.</p> <h2 id="lets-see-how-deserialisation-will-look-like" style="position:relative;"><a href="#lets-see-how-deserialisation-will-look-like" aria-label="lets see how deserialisation will look like permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Let’s see how deserialisation will look like</h2> <p>We can write the code accordingly to the serialisation, but this time the other way round. We need a method that takes the event type name and serialised event data. As a result, we’ll return the typed deserialised event.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartEventsSerde</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartEvent</span> <span class="token function">Deserialize</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventType<span class="token punctuation">,</span> <span class="token class-name">JsonDocument</span> document<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> data <span class="token operator">=</span> document<span class="token punctuation">.</span>RootElement<span class="token punctuation">;</span> <span class="token keyword">return</span> eventType <span class="token keyword">switch</span> <span class="token punctuation">{</span> <span class="token string">"shopping_cart_opened"</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"clientId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToClientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"product_item_added_to_shopping_cart"</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"productItem"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToPricedProductItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"product_item_removed_from_shopping_cart"</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemRemovedFromShoppingCart</span><span class="token punctuation">(</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"productItem"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToPricedProductItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"shopping_cart_confirmed"</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"confirmedAt"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLocalDateTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"shopping_cart_canceled"</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"shoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToShoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> data<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"canceledAt"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLocalDateTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Again, we’re making the pattern matching on the event type name. By that, we know precisely what event structure to expect and the type to deserialise.</p> <p><strong>Mapping complex types can be tedious, but we can compose it from tiny helpers that can make that process easier.</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Json</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonObject</span> <span class="token function">Object</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">KeyValuePair<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> JsonNode<span class="token punctuation">?</span><span class="token punctuation">></span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> nodes<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>nodes<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">KeyValuePair<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> JsonNode<span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> key<span class="token punctuation">,</span> <span class="token class-name">JsonNode<span class="token punctuation">?</span></span> node<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>key<span class="token punctuation">,</span> node<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonNode</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">ShoppingCartId</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonNode</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">ProductId</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonNode</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">ClientId</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonNode</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">Amount</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonNode</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">Quantity</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonNode</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">LocalDateTime</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonObject</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">Money</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">Object</span><span class="token punctuation">(</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"amount"</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Amount<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"currency"</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Currency<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonObject</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">Price</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span>Value<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">JsonObject</span> <span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">PricedProductItem</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">Object</span><span class="token punctuation">(</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"productId"</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span>ProductId<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"quantity"</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Quantity<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">Node</span><span class="token punctuation">(</span><span class="token string">"unitPrice"</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span>UnitPrice<span class="token punctuation">.</span><span class="token function">ToJson</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartId</span> <span class="token function">ToShoppingCartId</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> ShoppingCartId<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductId</span> <span class="token function">ToProductId</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> ProductId<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ClientId</span> <span class="token function">ToClientId</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> ClientId<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Currency</span> <span class="token function">ToCurrency</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> Enum<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Parse</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Currency<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Amount</span> <span class="token function">ToAmount</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> Amount<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetInt32</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Quantity</span> <span class="token function">ToQuantity</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> Quantity<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetUInt32</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Money</span> <span class="token function">ToMoney</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"amount"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToAmount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"currency"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToCurrency</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">LocalDateTime</span> <span class="token function">ToLocalDateTime</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> LocalDateTime<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>DateTimeOffset<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Price</span> <span class="token function">ToPrice</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">ToMoney</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">PricedProductItem</span> <span class="token function">ToPricedProductItem</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">JsonElement</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"productId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToProductId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"quantity"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToQuantity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"unitPrice"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToPrice</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We can make this process pretty straightforward if we’re consistent and do not take lazy shortcuts.</p> <h2 id="which-event-type-mapping-is-better" style="position:relative;"><a href="#which-event-type-mapping-is-better" aria-label="which event type mapping is better permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Which event type mapping is better?</h2> <p><strong>There’s no easy answer to that. It’s highly dependent on the development process, team constellation and tools you use.</strong> In C# and Java, serialisers like Json.NET, System.Text.Json or Jackson can take you pretty far. They contain not only the serialiser part but also enhanced mapping capabilities. Still, in environments like Node.JS, where serialisers are pretty dumb explicit approach may be rewarding.</p> <p>Going explicit may be tedious and more error-prone for dumb copy-paste mistakes. Conventional-based makes debugging serialisation issues much harder, as it’s tricky to find the source of the issue in the magical behind-the-scenes mappings.</p> <p><strong>An explicit approach, if made consistently, will make your type design easier.</strong> You won’t need to make rotten compromises like using just primitives in your domain logic or doing some additional mapping between a domain event and the wired technical ones.</p> <p>The choice is yours; I encourage you to try both ways, get familiar with it and feel the weak and strong points of those two approaches. Then you can find what works best for you and your project.</p> <p>See the full code for this article in: <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/190">https://github.com/oskardudycz/EventSourcing.NetCore/pull/190</a>.</p> <p><strong>Watch also more in the webinar:</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAAAAAAD6AF+hNEZAAABbUlEQVQoz4WS3W4TMRCF/dqgvgGXcM1LcIOQoKpIN222m+xmf/0zc2bGacljoN1NqFQhOFdj2eez54ydzhJV+asgWIv5gAqAl1/n+7tvnz9+uHn33gGXbb5W4LkEQEQgUXkDhlk+Pb+cz2dHDFUl5hQ8JYoxiZ0guvoPfpc4/fEvdADMzETkMHvVBNUQ98cdp1qOW429aLZsZVOEGPTaVVYxy2KndelCUIiYcOm1OP7s/WZT3Lb1NiuO1bG+78GX5w7DcPDU+Ya7Hc2dsYtJzYyYnzX3sSm7otrdteWPafOlejwQpTUKAPMFMVf1Lb5+Ejsxs/OjNzVeQlI1FZvaMozN5NNTUcYp4NKYdvsRgbNIGvYkM9TJEsYaD2NBWBY1VfHjVBfd0Pbd4/dpGg5FP/VhpoS2bAuBudcJvIqxUpYvACCEwMzRpxSJiIL3KZGqOL7O+R+6pL38k3VsTVP3Q3TM/ze/QTHjYVuUT+NvFbc119IGMYMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png" srcset="/static/5884d457fe2181ab0e490a1460ab913c/36ca5/2021-12-08-webinaresver.png 200w, /static/5884d457fe2181ab0e490a1460ab913c/a3397/2021-12-08-webinaresver.png 400w, /static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png 800w, /static/5884d457fe2181ab0e490a1460ab913c/8537d/2021-12-08-webinaresver.png 1200w, /static/5884d457fe2181ab0e490a1460ab913c/1a152/2021-12-08-webinaresver.png 1600w, /static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>And read in the versioning series:</strong></p> <ul> <li><a href="/en/simple_events_versioning_patterns/">Simple patterns for events schema versioning</a></li> <li><a href="/en/how_to_do_event_versioning/">How to (not) do the events versioning?</a></li> <li><a href="/en/fun_with_json_serialisation/">Fun with serial JSON</a></li> <li><a href="/en/how_to_map_event_type_by_convention/">Mapping event type by convention</a></li> <li><a href="/en/event_versioning_with_marten/">Event Versioning with Marten</a></li> <li><a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">Let’s take care of ourselves! Thoughts on compatibility</a></li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[The magic is that there is no magic. Or how to understand design patterns.]]>https://event-driven.io/en/the_magic_is_that_there_is_no_magic/https://event-driven.io/en/the_magic_is_that_there_is_no_magic/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2d00ba2a0ca1bb744bdd72974dea36af/8299d/2022-12-18-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABAACBf/EABcBAAMBAAAAAAAAAAAAAAAAAAADBAX/2gAMAwEAAhADEAAAATID0x+4dRlf/8QAGxAAAgIDAQAAAAAAAAAAAAAAAAECAxESE0L/2gAIAQEAAQUCyhEak4+qUuWx/8QAGREAAgMBAAAAAAAAAAAAAAAAAAEDBBEh/9oACAEDAQE/AbGqJnT/xAAXEQEAAwAAAAAAAAAAAAAAAAAAAhEy/9oACAECAQE/AY6U/8QAGhAAAgIDAAAAAAAAAAAAAAAAAAECEBIhcf/aAAgBAQAGPwLgsWJ1LVf/xAAbEAEAAwEAAwAAAAAAAAAAAAABABEhMVFhkf/aAAgBAQABPyG6rxTzsRrpY7PMCLqwYhqYst7+z//aAAwDAQACAAMAAAAQpN//xAAZEQACAwEAAAAAAAAAAAAAAAAAAREhsfD/2gAIAQMBAT8QaMcXou0f/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERcf/aAAgBAgEBPxCWlNH/xAAcEAEBAAICAwAAAAAAAAAAAAABEQAhMVFBceH/2gAIAQEAAT8QWidADR3esobAc4PAmS662b1kCAqb83EteOlmvmKRp9Z//9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/2d00ba2a0ca1bb744bdd72974dea36af/c60e9/2022-12-18-cover.jpg" srcset="/static/2d00ba2a0ca1bb744bdd72974dea36af/37402/2022-12-18-cover.jpg 200w, /static/2d00ba2a0ca1bb744bdd72974dea36af/4cda9/2022-12-18-cover.jpg 400w, /static/2d00ba2a0ca1bb744bdd72974dea36af/c60e9/2022-12-18-cover.jpg 800w, /static/2d00ba2a0ca1bb744bdd72974dea36af/6c738/2022-12-18-cover.jpg 1200w, /static/2d00ba2a0ca1bb744bdd72974dea36af/56dca/2022-12-18-cover.jpg 1600w, /static/2d00ba2a0ca1bb744bdd72974dea36af/8299d/2022-12-18-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>The magic is that there is no magic.</strong></p> <p>Many patterns perceived as complicated appear to be simple or even simplistic under the cover.</p> <p><strong>Take, for example, Event Sourcing.</strong> In a nutshell, you can append at the end of the stream and read all events from the stream. So append new business facts about the object or process. Then <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">read all events and build the current state from it</a> to know what happened and make the next decision.</p> <p><strong><a href="/en/cqrs_facts_and_myths_explained/">Or CQRS</a></strong>, you just slice your application by business operations and group them into two behaviours: reads and business logic. You’re getting by that predictability and possibility of on-point optimisation.</p> <p><strong><a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Or Outbox Pattern</a></strong>. You append the message together with the state change in the same database transaction. Then send the message asynchronously with retries etc., to ensure global process consistency.</p> <p>Yet, it’s not easy to explain, as people want to see magic where there are “just” smoke and mirrors.</p> <p>I’m saying “just” as it’s not easy to connect all the dots.</p> <p><strong><em>“Show me the real production code”.</em></strong> I hear that quite often.</p> <p>Yet, to learn patterns, you need isolated examples to practice and understand them. We need to put more effort into practising composition skills.</p> <p>Going straight into other people’s code won’t teach you to use patterns correctly, as you’ll see tradeoffs already applied. It’ll be hard to understand if they were applied on purpose or unintentionally.</p> <p><a href="/en/how_playing_on_guitar_helps_in_being_better_developer/">Before you play solos on guitar, you need to learn scales, chords and rhythm.</a></p> <p>The same is with architecture. You need to learn basic patterns and then analyse how to join them together.</p> <p><strong>Quite often on the Internet, what’s shown as best or worst patterns are compositions that either succeeded or failed.</strong> <a href="/en/event_streaming_is_not_event_sourcing/">We can find a lot of noise from the accidental complexity</a>. Rarely do authors present the patterns in a nutshell. They often show them mingled together as <em>the one way to rule them all</em>. While the presented configuration may be valid <strong>for them</strong>, it may be just one of the possible options <strong>for others.</strong></p> <p>We get so used to seeing complexity that when we’re faced with the isolated pattern, we’re saying <strong><em>“it cannot be that simple!”</em></strong>.</p> <p>Actually, it usually is, but it doesn’t always mean it’s easy. Composition with other patterns and with real-world tradeoffs brings complexity.</p> <p>As we learn the pattern, we should get to the source materials. <a href="/en/what_does_a_construction_failure_have_to_do_with_our_authorities/">Read what the original authors had in mind</a>. Understand the intended context in which it was designed. We might be quite surprised. Quite often, you may realise that we played the Chinese whispers.</p> <p><strong>The best way of learning is by doing.</strong> Experiment on a smaller scale, understand the tradeoffs, try different configurations and get it on production. Wash, rinse, repeat.</p> <p>For instance, when I started learning Event Sourcing, I created a <a href="https://github.com/oskardudycz/EventSourcing.NetCore">sample repository</a> to understand patterns and tooling without breaking my system at work. I tried to play with different ideas in a sandbox environment. Once I felt more comfortable, I took what worked to a regular project. Then I continued the next iterations with the other things I wanted to explore deeper.</p> <p><strong>Everything has pros and cons. We should not be <em>techarounding</em> the issue and believing that some tool will magically solve our use case.</strong> Too often, I see that people don’t know what problem they have to solve but have already chosen a tech stack. Then they try to bend the definition of the pattern to the selected tooling.</p> <p><strong>Distil the pattern, and think how it may compose with others, where it could work well, and where it may fail.</strong> Then find the best matching tools to help you.</p> <p>Realising that should make you a better developer and architect. You’ll be able to make on-point decisions and understand the tradeoffs you’re making.</p> <p>Still, beware! After that, <strong>POOF!</strong></p> <p><strong>Magic will be gone.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Share your story on Event Sourcing Live 2023]]>https://event-driven.io/en/share_your_story_on_event_sourcing_live/https://event-driven.io/en/share_your_story_on_event_sourcing_live/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/095a1724e3d5e503a9cf19618938ffff/c60e9/2022-12-06-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFgABAQEAAAAAAAAAAAAAAAAABAUA/8QAFgEBAQEAAAAAAAAAAAAAAAAAAQAC/9oADAMBAAIQAxAAAAEaoaNNHRNH/8QAGxAAAQQDAAAAAAAAAAAAAAAAAQACAxETFDL/2gAIAQEAAQUCikDDnDVtsVm5+l//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAYEQACAwAAAAAAAAAAAAAAAAAAAQIREv/aAAgBAgEBPwFSo2z/xAAaEAACAgMAAAAAAAAAAAAAAAAAAQIRECIx/9oACAEBAAY/ApNmyZxllY//xAAbEAACAgMBAAAAAAAAAAAAAAABEQAxECFBUf/aAAgBAQABPyGquoiShwgcxgut+wyNhSax/9oADAMBAAIAAwAAABAXL//EABYRAQEBAAAAAAAAAAAAAAAAAAEREP/aAAgBAwEBPxC0c//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QAf/EABsQAQADAQADAAAAAAAAAAAAAAEAESFREEGx/9oACAEBAAE/EFsUWgLS3k1t6ch9Hfk5TBQxujIAF0WOX3x//9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/095a1724e3d5e503a9cf19618938ffff/c60e9/2022-12-06-cover.jpg" srcset="/static/095a1724e3d5e503a9cf19618938ffff/37402/2022-12-06-cover.jpg 200w, /static/095a1724e3d5e503a9cf19618938ffff/4cda9/2022-12-06-cover.jpg 400w, /static/095a1724e3d5e503a9cf19618938ffff/c60e9/2022-12-06-cover.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Event Sourcing is a concept that helps to build the bridge between business and technical implementation.</strong> It’s a storage pattern that takes the business facts and uses them to model and handle business workflow. <a href="https://www.infoq.com/articles/architecture-trends-2022">InfoQ, in its Software Architecture and Design InfoQ Trends Report</a> puts it into the late majority, which means that it’s already widely adopted. I think that we’re not there yet, but it’s undeniable that Event Sourcing is one of the fastest-emerging concepts.</p> <p>That’s also why I’m passionate about sharing what I learned wherever I can, e.g. on this blog, <a href="/pl/training/">workshops</a>, and <a href="https://www.youtube.com/watch?v=Lu-skMQ-vAw&#x26;list=PLw-VZz_H4iiqUeEBDfGNendS0B3qIk-ps&#x26;index=7">talks</a>.</p> <p><strong>I’m happy to announce that this year, together with the DDDEU team, I’ll be responsible for curating next year’s Event Sourcing Live Conference lineup.</strong> I’m thrilled to be part of such an initiative, the only conference focused on Event Sourcing.</p> <p><strong>We want to prove that Event Sourcing is a highly practical pattern and show its real-world usage during the Event Sourcing Live Conference.</strong> We want to learn about both big successes adopting it and horror stories. Your stories.</p> <p>If you’re an Open Source maintainer of the Event Sourcing related tooling, that’s the right place to share your story. We want to connect event-sourced community and consider giving space for builders to show and tell their tooling. We’re still determining the specifics, but <a href="mailto:[email protected]">contact me</a>, and we’ll try to find a way to show your work.</p> <p>If you’re a practitioner with an intriguing use case but unsure about your speaking skills. Contact our team, especially <a href="mailto:[email protected]">Oskar Dudycz</a>, that’ll be curating this year’s edition. We’re open to do mentoring for the selected people.</p> <p><strong>We’re looking for talks made by practitioners sharing their experiences with the community and their case studies.</strong> It’s a chance for you to go down the rabbit hole with the audience and share more advanced scenarios. We encourage you to bring more technical details to show your use case. Still, the focus should be on the architecture and pattern implementation rather than tech specifics. Talks can also bring other aspects that are useful for the Event Sourcing applications, so CQRS, Event-Driven Architecture, Messaging etc.</p> <p><strong>As always, we’re looking for diversity in the content and representation.</strong> We want to show the whole variety of people using it and all the related practices. Like:</p> <ul> <li>Real-world case studies, experience reports</li> <li>Best and worst practices applied</li> <li>Specifics of event-driven modelling,</li> <li>Tooling and practices: CQRS, event stores, messaging, etc.</li> <li>Distilling the essence of Event Sourcing (similarities and differences with other techniques)</li> <li>Integrations with different patterns</li> <li>DevOps practices (e.g. running event stores, blue-green projections rebuilds, etc.)</li> <li>Handling Event Sourcing at scale</li> </ul> <p>We’re also interested in adjacent topics:</p> <ul> <li>Microservices</li> <li>Distributed systems</li> <li>Event-Driven Architecture.</li> </ul> <p><strong>Don’t be shy to <a href="https://2023.dddeurope.com/cfp/">send your proposal</a> and <a href="mailto:[email protected]">contact me</a> if you have any doubts. I’m here to help.</strong></p> <p>We’ll be running a set of speaker meetings to help you deliver the best proposal:</p> <ul> <li><a href="https://ti.to/dddbv/dddeu23-speaker-sessions">Learn about the DDD Europe 2023 CFP - December 6, 16:30-17:30 CET</a></li> <li><a href="https://ti.to/dddbv/dddeu23-speaker-sessions">How to do a Livecoding session - December 13, 16:30-17:30 CET</a></li> <li><a href="https://ti.to/dddbv/dddeu23-speaker-sessions">Learn about the EventSourcing Live CFP - December 21, 10:00-11:00 CET</a></li> </ul> <p>If you’re looking for inspiration, check my talk from the last edition and see <a href="https://www.youtube.com/@EventSourcingLive/videos">other talks</a>.</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/Lu-skMQ-vAw?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>I’m not sure about you, but I’m thrilled!</strong> Waiting for your proposal or joining the conference as an attendee. In both ways, we’ll have a good time learning and sharing our Event Sourcing journey!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Testing asynchronous processes with a little help from .NET Channels]]>https://event-driven.io/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/https://event-driven.io/en/testing_asynchronous_processes_with_a_little_help_from_dotnet_channels/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/9c05375d8d0e8027263b503b97c2587f/8299d/2022-12-04-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMEBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHLZKwayMuf/8QAGBABAQEBAQAAAAAAAAAAAAAAAQIAERP/2gAIAQEAAQUCiRfEGeSdzdO67//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABURAQEAAAAAAAAAAAAAAAAAAAAB/9oACAECAQE/AUf/xAAYEAADAQEAAAAAAAAAAAAAAAAAARExEP/aAAgBAQAGPwJ1wSukGbnP/8QAGhABAAMBAQEAAAAAAAAAAAAAAQAhMRFBcf/aAAgBAQABPyHHhcUhT8bOvW1FBX3EGtbn/9oADAMBAAIAAwAAABDXL//EABcRAAMBAAAAAAAAAAAAAAAAAAABESH/2gAIAQMBAT8QlQ1p/8QAFxEBAAMAAAAAAAAAAAAAAAAAAAEhQf/aAAgBAgEBPxDUqf/EAB0QAQEBAAICAwAAAAAAAAAAAAERACFBMVGBwdH/2gAIAQEAAT8Q7pG/kPvKUIZ74X83KS2lcCoRKp2aEzIqXA0K8u//2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/9c05375d8d0e8027263b503b97c2587f/c60e9/2022-12-04-cover.jpg" srcset="/static/9c05375d8d0e8027263b503b97c2587f/37402/2022-12-04-cover.jpg 200w, /static/9c05375d8d0e8027263b503b97c2587f/4cda9/2022-12-04-cover.jpg 400w, /static/9c05375d8d0e8027263b503b97c2587f/c60e9/2022-12-04-cover.jpg 800w, /static/9c05375d8d0e8027263b503b97c2587f/6c738/2022-12-04-cover.jpg 1200w, /static/9c05375d8d0e8027263b503b97c2587f/56dca/2022-12-04-cover.jpg 1600w, /static/9c05375d8d0e8027263b503b97c2587f/8299d/2022-12-04-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Let’s say that you have an event-driven application. It has an event bus that listens for events from asynchronous processes (e.g. Kafka consumer or <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">outbox pattern</a>).</strong> Once it gets them, it calls all event handlers registered for the particular event. It can be defined with the following interface:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IEventBus</span> <span class="token punctuation">{</span> <span class="token return-type class-name">Task</span> <span class="token function">Publish</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Typically, you should aim to have your business logic tested with the unit or integration tests, but it’s also worth writing end-to-end or <a href="/en/i_tested_on_production/">syntetic tests</a> for the most critical features.</p> <p><strong>Let’s say we’d like to check the result of the specific event handler (e.g. updated read model).</strong> As it’s asynchronous, we won’t know precisely when it happened. We could be <a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">polling continuously with retries</a> for changes to our read model. Yet, that won’t be efficient and will make our tests less predictable and running longer. How to improve that?</p> <p><strong>We could benefit from <a href="https://learn.microsoft.com/en-us/dotnet/core/extensions/channels">Channels</a>.</strong> They’re a way of safely handling multi-threaded concurrency in a reliable and performant manner. They’re using producer/consumer and can be used in publish/subscribe manner.</p> <p>Let’s define the <em>EventListener</em> class to record all the events <em>EventBus</em> got.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventListener</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Channel<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> events <span class="token operator">=</span> Channel<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">CreateUnbounded</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ChannelReader<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> Reader <span class="token operator">=></span> events<span class="token punctuation">.</span>Reader<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ChannelWriter<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> Writer <span class="token operator">=></span> events<span class="token punctuation">.</span>Writer<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> events<span class="token punctuation">.</span>Writer<span class="token punctuation">.</span><span class="token function">WriteAsync</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">WaitForProcessing</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> item <span class="token keyword">in</span> events<span class="token punctuation">.</span>Reader<span class="token punctuation">.</span><span class="token function">ReadAllAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>item<span class="token punctuation">.</span><span class="token function">Equals</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"No events were found"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re setting up the unbounded channel. It means that we’re not putting any restrictions on it. It may have multiple writers and readers; there’re no limitations on the number of produced messages in the channel.</p> <p><strong>Channels are highly configurable and extendible, but in our case, we just need to be able to write to it and wait until the message is processed.</strong> We can do it using <em>ReadAllAsync</em> method that returns <em>IAsyncEnumerable</em>. Once the event is published and written to the channel, the next iteration of async foreach will be triggered for the reader automatically. If it matches the expected event, then we’re just finishing waiting.</p> <p>Now, we can write a decorator for our event bus that we’ll be catching all handled events and forward them to <em>EventListener</em>:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventCatcher</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventBus</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">EventListener</span> listener<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IEventBus</span> eventBus<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">EventCatcher</span><span class="token punctuation">(</span><span class="token class-name">EventListener</span> listener<span class="token punctuation">,</span> <span class="token class-name">IEventBus</span> eventBus<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>listener <span class="token operator">=</span> listener<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventBus <span class="token operator">=</span> eventBus<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Publish</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> eventBus<span class="token punctuation">.</span><span class="token function">Publish</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> listener<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ConfigureAwait</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>It’s a simple wrapper that takes the original event bus and pushes the event to the listener after events are handled.</strong> If the event bus is synchronous and in-memory, then we’re sure all the event handlers were called.</p> <p>We can override the original setup following way in our end-to-end code:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> eventListener <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventListener</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> services <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddScoped</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEventBus<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>sp <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventCatcher</span><span class="token punctuation">(</span> eventListener<span class="token punctuation">,</span> sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>EventBus<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span></code></pre></div> <p>Thanks to that, we can write the following test:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">AddUser_ShouldEventuallyUpdateUserDetailsReadModel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Given</span> <span class="token class-name"><span class="token keyword">var</span></span> userId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> userName <span class="token operator">=</span> <span class="token string">"Oscar the Grouch"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">AddUser</span><span class="token punctuation">(</span>userId<span class="token punctuation">,</span> userName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> userAdded <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">UserAdded</span><span class="token punctuation">(</span>userId<span class="token punctuation">,</span> userName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// When</span> <span class="token keyword">await</span> commandHandler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Then</span> <span class="token keyword">await</span> eventListener<span class="token punctuation">.</span><span class="token function">WaitForProcessing</span><span class="token punctuation">(</span>userAdded <span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> userDetails <span class="token operator">=</span> <span class="token return-type class-name">awai</span> <span class="token function">GetUserDetails</span><span class="token punctuation">(</span>userId<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> userDetails<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">NotBeNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> userDetails<span class="token punctuation">.</span>Id<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>userId<span class="token punctuation">)</span><span class="token punctuation">;</span> userDetails<span class="token punctuation">.</span>Name<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>userName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Thanks to that, we don’t need to try continuous polling but finish our test at the right time.</p> <p><strong>If you’re curious when that could be useful, check the <a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed">“Implementing Distributed Processes”</a> webinar:</strong></p> <p><a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAQBAgX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB24sQiOlf/8QAFhABAQEAAAAAAAAAAAAAAAAAAhAS/9oACAEBAAEFAow93//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABgQAAIDAAAAAAAAAAAAAAAAAAERECCR/9oACAEBAAY/AoKW0//EABoQAQABBQAAAAAAAAAAAAAAAAEAEBEhMXH/2gAIAQEAAT8hW0FdRqYnmrP/2gAMAwEAAgADAAAAEFg//8QAFREBAQAAAAAAAAAAAAAAAAAAABH/2gAIAQMBAT8QV//EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAECAQE/EFf/xAAaEAEBAQEBAQEAAAAAAAAAAAABEQAxIYHw/9oACAEBAAE/EKC8fOYFYCz000jUrT8n6aD0uCczR83/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Webinar" title="Webinar" src="/static/af76836ad7c0be0e9b5da61d62bfa0e6/c60e9/2022-12-04-webinar.jpg" srcset="/static/af76836ad7c0be0e9b5da61d62bfa0e6/37402/2022-12-04-webinar.jpg 200w, /static/af76836ad7c0be0e9b5da61d62bfa0e6/4cda9/2022-12-04-webinar.jpg 400w, /static/af76836ad7c0be0e9b5da61d62bfa0e6/c60e9/2022-12-04-webinar.jpg 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p>Or check:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/HotelManagement">Code Samples</a>,</li> <li><a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox, Inbox patterns and delivery guarantees explained</a>,</li> <li><a href="/en/saga_process_manager_distributed_transactions/">Saga and Process Manager - distributed processes in practice</a>,</li> <li><a href="/en/event_driven_distributed_processes_by_example/">Event-driven distributed processes by example</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Mapping event type by convention]]>https://event-driven.io/en/how_to_map_event_type_by_convention/https://event-driven.io/en/how_to_map_event_type_by_convention/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/455631b320975f8a96d281795939b0c6/8299d/2022-11-27-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAECAwT/xAAWAQEBAQAAAAAAAAAAAAAAAAABAwT/2gAMAwEAAhADEAAAAdbUq57Bif/EABoQAQEAAgMAAAAAAAAAAAAAAAIBAAMQERL/2gAIAQEAAQUCr6cfrhaokBBM/8QAFREBAQAAAAAAAAAAAAAAAAAAECH/2gAIAQMBAT8Bh//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABkQAAIDAQAAAAAAAAAAAAAAAAARARASIv/aAAgBAQAGPwLKOYdORV//xAAdEAACAQQDAAAAAAAAAAAAAAABEQAQITFRQWFx/9oACAEBAAE/IeUbNuaw3iDFwoWGueotCT7T/9oADAMBAAIAAwAAABCAD//EABURAQEAAAAAAAAAAAAAAAAAABAx/9oACAEDAQE/EIP/xAAVEQEBAAAAAAAAAAAAAAAAAAAQIf/aAAgBAgEBPxCn/8QAHBABAQACAgMAAAAAAAAAAAAAAREAURAhMZHB/9oACAEBAAE/EHQdHgSG3BYou31iWqNW5RIEBCBkW3cdeuP/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/455631b320975f8a96d281795939b0c6/c60e9/2022-11-27-cover.jpg" srcset="/static/455631b320975f8a96d281795939b0c6/37402/2022-11-27-cover.jpg 200w, /static/455631b320975f8a96d281795939b0c6/4cda9/2022-11-27-cover.jpg 400w, /static/455631b320975f8a96d281795939b0c6/c60e9/2022-11-27-cover.jpg 800w, /static/455631b320975f8a96d281795939b0c6/6c738/2022-11-27-cover.jpg 1200w, /static/455631b320975f8a96d281795939b0c6/56dca/2022-11-27-cover.jpg 1600w, /static/455631b320975f8a96d281795939b0c6/8299d/2022-11-27-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Events are an essential block of Event-Driven Architecture. They represent business facts that happened in our system.</strong> We can use them to integrate business workflow steps, store them to use them later in our business logic, or get insights about our process. They’re both business concepts representing the checkpoints of our workflow, and technical messages changed back and forth into a series of ones and zeros. This process is called (de)serialisation. We have plenty of options to choose from in serialisation formats. The major split is for text-based and binary formats. Text-based are, e.g. JSON and XML. Their advantage is that you can read them in any text editor, are (more or less) human-readable, popular and have much tooling around them; they’re also standardised. Yet, they have flaws, like the inability to easily express precise numeric, date and time formats; they take more space. That’s where binary formats can help. The most popular are <a href="https://developers.google.com/protocol-buffers">Protobuf</a> and <a href="https://avro.apache.org/">Avro</a>.</p> <p><strong>No matter your choice, you’ll still need to define the mapping between the code type representing your event and serialised message.</strong> This part is also essential in maintaining the evolution/versioning of events. I wrote about that in my other articles: <a href="/en/simple_events_versioning_patterns/">Simple patterns for events schema versioning</a> and <a href="/en/event_versioning_with_marten/">Event Versioning with Marten</a>. Check also Greg Young book <a href="https://leanpub.com/esversioning/read">Versioning in an Event Sourced System</a>.</p> <p><strong>When serialising type to bytes, you need to store the type name. You can do that explicitly by manually defining mapping or using a convention-based approach.</strong> Both have pros and cons. Manual can be a bit repetitive and yet another thing to remember. Convention-based is more magical, and if we forget how it works, then surprisingly, we can break our mapping. How? The most straightforward approach in languages like C# or Java is to use the full class name. We can get the text name from our event class. It contains both namespace/package paths so that we won’t mistake it with other events named the same but in different locations. Yet, if we’re refactoring and moving code from one place to another or fixing an accidental typo, we can change the event type name. Once we did that, our mapping was broken, as the stored event type name won’t match the new location.</p> <p>Of course, we can fix it by introducing manual mapping and telling our mapping logic that for this event type name, we’d like to use the new class name (or just a different class if we want to map the old event payload to the new version).</p> <p><strong>Let’s see how we can get the best out of those worlds and possibly use conventions by default but also have the option to switch to manual mapping.</strong> We need to define maps containing class-to-event type name mapping and the other way round. In C# this could look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventTypeMapper</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token class-name">EventTypeMapper</span> Instance <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ConcurrentDictionary<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> Type<span class="token punctuation">?</span><span class="token punctuation">></span></span> typeMap <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ConcurrentDictionary<span class="token punctuation">&lt;</span>Type<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span> typeNameMap <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We also defined a singleton instance, as we’d like to have it as a global store to increase the performance (read more in <a href="/en/memoization_a_useful_pattern_for_quick_optimisation/">Memoization, a useful pattern for quick optimization</a>).</p> <p>As I mentioned, we’d like to have option to define the custom, explicit mappings. We can do that as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventTypeMapper</span> <span class="token punctuation">{</span> <span class="token comment">// (…)</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token generic-method"><span class="token function">AddCustomMap</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">AddCustomMap</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">T</span><span class="token punctuation">)</span><span class="token punctuation">,</span> eventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddCustomMap</span><span class="token punctuation">(</span><span class="token class-name">Type</span> eventType<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">)</span> <span class="token punctuation">{</span> typeNameMap<span class="token punctuation">.</span><span class="token function">AddOrUpdate</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> eventTypeName<span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> typeName<span class="token punctuation">)</span> <span class="token operator">=></span> typeName<span class="token punctuation">)</span><span class="token punctuation">;</span> typeMap<span class="token punctuation">.</span><span class="token function">AddOrUpdate</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> eventType<span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> type<span class="token punctuation">)</span> <span class="token operator">=></span> type<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re assigning mapping for both ways by providing the class type and event type name.</p> <p>How to define the resolution methods? We can do it like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventTypeMapper</span> <span class="token punctuation">{</span> <span class="token comment">// (…)</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token generic-method"><span class="token function">ToName</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEventType<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">ToName</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">TEventType</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">ToName</span><span class="token punctuation">(</span><span class="token class-name">Type</span> eventType<span class="token punctuation">)</span> <span class="token operator">=></span> typeNameMap<span class="token punctuation">.</span><span class="token function">GetOrAdd</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> eventTypeName <span class="token operator">=</span> eventType<span class="token punctuation">.</span>FullName<span class="token operator">!</span><span class="token punctuation">;</span> typeMap<span class="token punctuation">.</span><span class="token function">TryAdd</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> eventType<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> eventTypeName<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">Type<span class="token punctuation">?</span></span> <span class="token function">ToType</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">)</span> <span class="token operator">=></span> typeMap<span class="token punctuation">.</span><span class="token function">GetOrAdd</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> type <span class="token operator">=</span> <span class="token function">GetFirstMatchingTypeFromCurrentDomainAssembly</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> typeNameMap<span class="token punctuation">.</span><span class="token function">TryAdd</span><span class="token punctuation">(</span>type<span class="token punctuation">,</span> eventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> type<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">Type<span class="token punctuation">?</span></span> <span class="token function">GetFirstMatchingTypeFromCurrentDomainAssembly</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> typeName<span class="token punctuation">)</span> <span class="token operator">=></span> AppDomain<span class="token punctuation">.</span>CurrentDomain<span class="token punctuation">.</span><span class="token function">GetAssemblies</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>a <span class="token operator">=></span> a<span class="token punctuation">.</span><span class="token function">GetTypes</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>AssemblyQualifiedName <span class="token operator">==</span> typeName <span class="token operator">||</span> x<span class="token punctuation">.</span>FullName <span class="token operator">==</span> typeName <span class="token operator">||</span> x<span class="token punctuation">.</span>Name <span class="token operator">==</span> typeName<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">FirstOrDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>The logic for mapping is simple, we’re either using already existing one or trying to resolve type by convention.</strong> <em>GetFirstMatchingTypeFromCurrentDomainAssembly</em> method is responsible for that. We can define any other convention if we’d like to. I’m using <em>ConcurrentDictionary</em> instead of regular <em>Dictionary</em> to make operations <a href="https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentdictionary-2?view=net-8.0#thread-safety">Thread safe</a>.</p> <p>Note that the <em>GetFirstMatchingTypeFromCurrentDomainAssembly</em> is an expensive operation that will be called for each event type. Yet, it will be only called once, then resolved type will be cached, and you won’t get further performance hits. If you’re afraid of that and know the event types upfront, then you can preload types at the startup. If you’re in the .NET space, you can also consider using <a href="https://github.com/dadhi/ImTools">ImHashMap</a> which is also thread safe and much faster than regular <em>ConcurrentDictionary</em>.</p> <p>The final mapper class will look as follow:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventTypeMapper</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token class-name">EventTypeMapper</span> Instance <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ConcurrentDictionary<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> Type<span class="token punctuation">?</span><span class="token punctuation">></span></span> typeMap <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ConcurrentDictionary<span class="token punctuation">&lt;</span>Type<span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span> typeNameMap <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token generic-method"><span class="token function">AddCustomMap</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">AddCustomMap</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">T</span><span class="token punctuation">)</span><span class="token punctuation">,</span> eventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddCustomMap</span><span class="token punctuation">(</span><span class="token class-name">Type</span> eventType<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">)</span> <span class="token punctuation">{</span> typeNameMap<span class="token punctuation">.</span><span class="token function">AddOrUpdate</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> eventTypeName<span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> typeName<span class="token punctuation">)</span> <span class="token operator">=></span> typeName<span class="token punctuation">)</span><span class="token punctuation">;</span> typeMap<span class="token punctuation">.</span><span class="token function">AddOrUpdate</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> eventType<span class="token punctuation">,</span> <span class="token punctuation">(</span>_<span class="token punctuation">,</span> type<span class="token punctuation">)</span> <span class="token operator">=></span> type<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token generic-method"><span class="token function">ToName</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEventType<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">ToName</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">TEventType</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">ToName</span><span class="token punctuation">(</span><span class="token class-name">Type</span> eventType<span class="token punctuation">)</span> <span class="token operator">=></span> typeNameMap<span class="token punctuation">.</span><span class="token function">GetOrAdd</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> eventTypeName <span class="token operator">=</span> eventType<span class="token punctuation">.</span>FullName<span class="token operator">!</span><span class="token punctuation">;</span> typeMap<span class="token punctuation">.</span><span class="token function">TryAdd</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> eventType<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> eventTypeName<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">Type<span class="token punctuation">?</span></span> <span class="token function">ToType</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">)</span> <span class="token operator">=></span> typeMap<span class="token punctuation">.</span><span class="token function">GetOrAdd</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> type <span class="token operator">=</span> TypeProvider<span class="token punctuation">.</span><span class="token function">GetFirstMatchingTypeFromCurrentDomainAssembly</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> typeNameMap<span class="token punctuation">.</span><span class="token function">TryAdd</span><span class="token punctuation">(</span>type<span class="token punctuation">,</span> eventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> type<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>In Java, we could define that accordingly:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">EventTypeMapper</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">EventTypeMapper</span> <span class="token class-name">Instance</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">EventTypeMapper</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">,</span> <span class="token class-name">Optional</span><span class="token punctuation">&lt;</span><span class="token class-name">Class</span><span class="token punctuation">></span><span class="token punctuation">></span></span> typeMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Map</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Class</span><span class="token punctuation">,</span> <span class="token class-name">String</span><span class="token punctuation">></span></span> typeNameMap <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">HashMap</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">T</span><span class="token punctuation">></span></span><span class="token keyword">void</span> <span class="token class-name">AddCustomMap</span><span class="token punctuation">(</span><span class="token class-name">Class</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">T</span><span class="token punctuation">></span></span> eventType<span class="token punctuation">,</span> <span class="token class-name">String</span> mappedEventTypeName<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name">Instance</span><span class="token punctuation">.</span>typeNameMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> mappedEventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">Instance</span><span class="token punctuation">.</span>typeMap<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span>mappedEventTypeName<span class="token punctuation">,</span> eventType<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">String</span> <span class="token function">toName</span><span class="token punctuation">(</span><span class="token class-name">Class</span> eventType<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token class-name">Instance</span><span class="token punctuation">.</span>typeNameMap<span class="token punctuation">.</span><span class="token function">computeIfAbsent</span><span class="token punctuation">(</span> eventType<span class="token punctuation">,</span> c <span class="token operator">-></span> c<span class="token punctuation">.</span><span class="token function">getTypeName</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">Optional</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Class</span><span class="token punctuation">></span></span> <span class="token function">toClass</span><span class="token punctuation">(</span><span class="token class-name">String</span> eventTypeName<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token class-name">Instance</span><span class="token punctuation">.</span>typeMap<span class="token punctuation">.</span><span class="token function">computeIfAbsent</span><span class="token punctuation">(</span> eventTypeName<span class="token punctuation">,</span> c <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token class-name">Optional</span><span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span><span class="token class-name">Class</span><span class="token punctuation">.</span><span class="token function">forName</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">ClassNotFoundException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token class-name">Optional</span><span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We can use such a mapper directly in the serialiser:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventSerializer</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">EventTypeMapper</span> eventTypeMapper<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">EventSerialiser</span><span class="token punctuation">(</span><span class="token class-name">EventTypeMapper</span> eventTypeMapper<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventTypeMapper <span class="token operator">=</span> eventTypeMapper<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">)</span></span> <span class="token generic-method"><span class="token function">Serialize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">T</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> typeName <span class="token operator">=</span> eventTypeMapper<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ToName</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>typeName<span class="token punctuation">,</span> JsonSerializer<span class="token punctuation">.</span><span class="token function">Serialize</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">?</span></span> <span class="token function">Deserialize</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> json<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> type <span class="token operator">=</span> eventTypeMapper<span class="token punctuation">.</span><span class="token function">ToType</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>type <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> JsonSerializer<span class="token punctuation">.</span><span class="token function">Deserialize</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> type<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Is it the best approach?</strong> It depends on personal preferences. My experience is that tedious, repetitive code leads to stupid mistakes. A bit of magic can create bugs that are harder to find, so you need to pick your poison. What’s most important is that such mapping is not blocking you in any way from changing your approach in the future.</p> <p>If you don’t like such magic, check <a href="/en/explicit_events_serialisation_in_event_sourcing/">Explicit events serialisation in Event Sourcing</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to get all messages through Postgres logical replication]]>https://event-driven.io/en/how_to_get_all_messages_through_postgres_logical_replication/https://event-driven.io/en/how_to_get_all_messages_through_postgres_logical_replication/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/43b6f170e08908f1b2e668b3a132593a/8299d/2022-11-20-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAUBAgP/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABXwwTxc1E/8QAGhAAAgMBAQAAAAAAAAAAAAAAAQIAAxESE//aAAgBAQABBQJF1OmrJOyxOqydPnP/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAZEAADAQEBAAAAAAAAAAAAAAAAATERAiH/2gAIAQEABj8Ce+MtKPqYayn/xAAbEAEAAgMBAQAAAAAAAAAAAAABABEhMVFBYf/aAAgBAQABPyEi0sMpm5YgClY5LEHsHJE2CwsX6+T/2gAMAwEAAgADAAAAEEP/AP/EABYRAQEBAAAAAAAAAAAAAAAAAAARIf/aAAgBAwEBPxDIj//EABYRAQEBAAAAAAAAAAAAAAAAAAARAf/aAAgBAgEBPxCaj//EABsQAQADAQEBAQAAAAAAAAAAAAEAESFRMUGx/9oACAEBAAE/EENC56GyuhX5C7A1jV2aKrqB9KSOi33m+SyQitMyCBYar4n/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/43b6f170e08908f1b2e668b3a132593a/c60e9/2022-11-20-cover.jpg" srcset="/static/43b6f170e08908f1b2e668b3a132593a/37402/2022-11-20-cover.jpg 200w, /static/43b6f170e08908f1b2e668b3a132593a/4cda9/2022-11-20-cover.jpg 400w, /static/43b6f170e08908f1b2e668b3a132593a/c60e9/2022-11-20-cover.jpg 800w, /static/43b6f170e08908f1b2e668b3a132593a/6c738/2022-11-20-cover.jpg 1200w, /static/43b6f170e08908f1b2e668b3a132593a/56dca/2022-11-20-cover.jpg 1600w, /static/43b6f170e08908f1b2e668b3a132593a/8299d/2022-11-20-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In an earlier article, I described <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">Push-based Outbox Pattern with Postgres Logical Replication</a>. The idea is to store the outgoing message (e.g. event) in the same database transaction together with the state change. Thanks to that, we’re ensuring that message won’t be lost, and our business workflow will proceed and become consistent.</p> <p><strong>Postgres can help and inform us when a new message is appended.</strong> We can use the native mechanism of <a href="/en/relational_databases_are_event_stores/">Write-Ahead Log (WAL)</a> together with logical replication.</p> <p><strong>The Write-Ahead Log is a centrepiece of Postgres.</strong> Each insert, update, and delete is logged in the order of appearance and then applied to tables on the transaction commit. Logical replication takes the traditional approach to the next level. Instead of sending the raw binary stream of backed-up database files, we’re sending a stream of changes that were recorded in the Write-Ahead Log.</p> <p><strong>Write-Ahead Log is an ephemeral structure.</strong> Unless we tell the database to keep it longer, records may be pruned after a successful transaction commit. It is also done to optimise disk storage. When we create logical replication publication, we tell Postgres to keep WAL entries, as we’d like to get them through notifications.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">CreatePublication</span><span class="token punctuation">(</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> _<span class="token punctuation">,</span> publicationName<span class="token punctuation">,</span> tableName<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> dataSource <span class="token operator">=</span> NpgsqlDataSource<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dataSource<span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"CREATE PUBLICATION </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">publicationName</span><span class="token punctuation">}</span></span><span class="token string"> FOR TABLE </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">tableName</span><span class="token punctuation">}</span></span><span class="token string">;"</span></span><span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Yet if we create a subscription:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>CreateReplicationSlotResult<span class="token punctuation">></span></span> <span class="token function">CreateSubscription</span><span class="token punctuation">(</span> <span class="token class-name">LogicalReplicationConnection</span> connection<span class="token punctuation">,</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> connection<span class="token punctuation">.</span><span class="token function">CreatePgOutputReplicationSlot</span><span class="token punctuation">(</span> options<span class="token punctuation">.</span>SlotName<span class="token punctuation">,</span> <span class="token named-parameter punctuation">slotSnapshotInitMode</span><span class="token punctuation">:</span> LogicalSlotSnapshotInitMode<span class="token punctuation">.</span>Export<span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Created</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>TableName<span class="token punctuation">,</span> result<span class="token punctuation">.</span>SnapshotName<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>And subscribe for the notifications:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> <span class="token function">Subscribe</span><span class="token punctuation">(</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">EnumeratorCancellation</span></span><span class="token punctuation">]</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> slotName<span class="token punctuation">,</span> publicationName<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> conn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">LogicalReplicationConnection</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> slot <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationSlot</span><span class="token punctuation">(</span>slotName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> message <span class="token keyword">in</span> conn<span class="token punctuation">.</span><span class="token function">StartReplication</span><span class="token punctuation">(</span>slot<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationOptions</span><span class="token punctuation">(</span>publicationName<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>message <span class="token keyword">is</span> <span class="token class-name">InsertMessage</span> insertMessage<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> <span class="token keyword">await</span> InsertMessageHandler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>insertMessage<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> conn<span class="token punctuation">.</span><span class="token function">SetReplicationStatus</span><span class="token punctuation">(</span>message<span class="token punctuation">.</span>WalEnd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">SendStatusUpdate</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>We may realise that we only got the newly appended records after the publication was created.</strong> That’s because Postgres didn’t know before that we’d like to keep WAL entries and pruned them. That’s not a big deal if we’re starting with a new deployment or the best greenfield project. Yet, if we already had <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">a pull-based outbox implementation</a>, we might also want to get the <em>old</em> messages. How to do that?</p> <p>Let’s get back to the subscription setup:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> connection<span class="token punctuation">.</span><span class="token function">CreatePgOutputReplicationSlot</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>SlotName<span class="token punctuation">,</span> <span class="token named-parameter punctuation">slotSnapshotInitMode</span><span class="token punctuation">:</span> LogicalSlotSnapshotInitMode<span class="token punctuation">.</span>Export<span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Created</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>TableName<span class="token punctuation">,</span> result<span class="token punctuation">.</span>SnapshotName<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It calls internally <a href="https://www.postgresql.org/docs/15/protocol-replication.html#PROTOCOL-REPLICATION-CREATE-REPLICATION-SLOT">CREATE_REPLICATION_SLOT</a> function:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">CREATE_REPLICATION_SLOT events_slot <span class="token return-type class-name">LOGICAL</span> <span class="token function">pgoutput</span><span class="token punctuation">(</span>SNAPSHOT 'export'<span class="token punctuation">)</span></code></pre></div> <p>We’re passing the bit enigmatic parameter <em>SNAPSHOT ‘export’</em>. Before I explain it, <strong>let’s stop for a moment and briefly discuss how the Postgres transaction works.</strong></p> <p>The transaction may contain multiple statements. With the cadence that depends on the transaction level, Postgres creates snapshots. Snapshot is a frozen state of the database is at a certain point in time:</p> <ul> <li>for <em>READ COMMITED</em> snapshot is created after each committed statement,</li> <li><em>REPEATABLE READ</em> and <em>SERLIALIZABLE</em> create a snapshot at the beginning and keep it consistent throughout the transaction, even if other sessions commit transactions.</li> </ul> <p><strong>Snapshot is usually kept until the transaction exists and then removed.</strong> Yet, we’re taking a Postgres here. There needs to be more in that, right? If you’re a regular reader of my blog <a href="/en/relational_databases_are_event_stores/">you already know <em>pg_current_snapshot</em> function</a>. It returns information about the current snapshot. Postgres have more functions like that; for instance, <a href="https://pgpedia.info/p/pg_export_snapshot.html">pg_export_snapshot</a> allows to keep snapshot longer than the transaction lifetime. Why would we need it? For example, to do a database backup, <a href="https://www.postgresql.org/docs/current/app-pgdump.html">pg_dump</a> uses it internally to become fault tolerant. We wouldn’t want the backed-up data changed during the process, right?</p> <p><strong>Export snapshot feature is also used while creating the replication slot.</strong> If we specify the <em>SNAPSHOT ‘export’</em> parameter when creating the replication slot, it will create the snapshot automatically and return its id. We can use it snapshot to get the existing data at the moment we created replication slot. All newer ones will be sent through logical replication.</p> <p>To read existing records, we need to create a transaction with at least a <em>REPEATABLE READ</em> transaction level and set the transaction snapshot to the id we get from the previous step. That will make our reads access only data at the snapshotted point in time.</p> <p>In C#, the code can look like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> transaction <span class="token operator">=</span> <span class="token keyword">await</span> connection<span class="token punctuation">.</span><span class="token function">BeginTransactionAsync</span><span class="token punctuation">(</span> IsolationLevel<span class="token punctuation">.</span>RepeatableRead<span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NpgsqlCommand</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"SET TRANSACTION SNAPSHOT '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">snapshotName</span><span class="token punctuation">}</span></span><span class="token string">';"</span></span><span class="token punctuation">,</span> connection<span class="token punctuation">,</span> transaction <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> command<span class="token punctuation">.</span><span class="token function">ExecuteScalarAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then we can poll the records using a regular <em>SELECT</em> statement on the outbox table. The method will look as follows.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> <span class="token function">QueryTransactionSnapshot</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">NpgsqlConnection</span> connection<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> snapshotName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> tableName<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>NpgsqlDataReader<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span><span class="token punctuation">></span></span> map<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">EnumeratorCancellation</span></span><span class="token punctuation">]</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> transaction <span class="token operator">=</span> <span class="token keyword">await</span> connection<span class="token punctuation">.</span><span class="token function">BeginTransactionAsync</span><span class="token punctuation">(</span> IsolationLevel<span class="token punctuation">.</span>RepeatableRead<span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NpgsqlCommand</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"SET TRANSACTION SNAPSHOT '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">snapshotName</span><span class="token punctuation">}</span></span><span class="token string">';"</span></span><span class="token punctuation">,</span> connection<span class="token punctuation">,</span> transaction <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> command<span class="token punctuation">.</span><span class="token function">ExecuteScalarAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> cmd <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NpgsqlCommand</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"SELECT * FROM </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">tableName</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> connection<span class="token punctuation">,</span> transaction <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> reader <span class="token operator">=</span> <span class="token keyword">await</span> cmd<span class="token punctuation">.</span><span class="token function">ExecuteReaderAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token keyword">await</span> reader<span class="token punctuation">.</span><span class="token function">ReadAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">map</span><span class="token punctuation">(</span>reader<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>The final code for our subscription that can do a full setup and read snapshotted data will look like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IEventsSubscription</span> <span class="token punctuation">{</span> <span class="token return-type class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> <span class="token function">Subscribe</span><span class="token punctuation">(</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventsSubscription</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventsSubscription</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>CreateReplicationSlotResult<span class="token punctuation">></span></span> <span class="token function">CreateSubscription</span><span class="token punctuation">(</span> <span class="token class-name">LogicalReplicationConnection</span> connection<span class="token punctuation">,</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token keyword">await</span> <span class="token function">PublicationExists</span><span class="token punctuation">(</span>options<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">await</span> <span class="token function">CreatePublication</span><span class="token punctuation">(</span>options<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">ReplicationSlotExists</span><span class="token punctuation">(</span>options<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">AlreadyExists</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> connection<span class="token punctuation">.</span><span class="token function">CreatePgOutputReplicationSlot</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>SlotName<span class="token punctuation">,</span> <span class="token named-parameter punctuation">slotSnapshotInitMode</span><span class="token punctuation">:</span> LogicalSlotSnapshotInitMode<span class="token punctuation">.</span>Export<span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Created</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>TableName<span class="token punctuation">,</span> result<span class="token punctuation">.</span>SnapshotName<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> <span class="token function">Subscribe</span><span class="token punctuation">(</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">EnumeratorCancellation</span></span><span class="token punctuation">]</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> slotName<span class="token punctuation">,</span> publicationName<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> conn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">LogicalReplicationConnection</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">CreateSubscription</span><span class="token punctuation">(</span>conn<span class="token punctuation">,</span> options<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token keyword">is</span> <span class="token class-name">Created</span> created<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> <span class="token function">ReadExistingEventsFromSnapshot</span><span class="token punctuation">(</span> created<span class="token punctuation">.</span>SnapshotName<span class="token punctuation">,</span> options<span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token class-name"><span class="token keyword">var</span></span> slot <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationSlot</span><span class="token punctuation">(</span>slotName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> message <span class="token keyword">in</span> conn<span class="token punctuation">.</span><span class="token function">StartReplication</span><span class="token punctuation">(</span> slot<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationOptions</span><span class="token punctuation">(</span>publicationName<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>message <span class="token keyword">is</span> <span class="token class-name">InsertMessage</span> insertMessage<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> <span class="token keyword">await</span> InsertMessageHandler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>insertMessage<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> conn<span class="token punctuation">.</span><span class="token function">SetReplicationStatus</span><span class="token punctuation">(</span>message<span class="token punctuation">.</span>WalEnd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">SendStatusUpdate</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span><span class="token keyword">bool</span><span class="token punctuation">></span></span> <span class="token function">ReplicationSlotExists</span><span class="token punctuation">(</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> slotName<span class="token punctuation">,</span> _<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> dataSource <span class="token operator">=</span> NpgsqlDataSource<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">await</span> dataSource<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span> <span class="token string">"pg_replication_slots"</span><span class="token punctuation">,</span> <span class="token string">"slot_name = $1"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span> slotName <span class="token punctuation">}</span><span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">CreatePublication</span><span class="token punctuation">(</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> _<span class="token punctuation">,</span> publicationName<span class="token punctuation">,</span> tableName<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> dataSource <span class="token operator">=</span> NpgsqlDataSource<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dataSource<span class="token punctuation">.</span><span class="token function">Execute</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"CREATE PUBLICATION </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">publicationName</span><span class="token punctuation">}</span></span><span class="token string"> FOR TABLE </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">tableName</span><span class="token punctuation">}</span></span><span class="token string">;"</span></span><span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span><span class="token keyword">bool</span><span class="token punctuation">></span></span> <span class="token function">PublicationExists</span><span class="token punctuation">(</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> slotName<span class="token punctuation">,</span> _<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> dataSource <span class="token operator">=</span> NpgsqlDataSource<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">await</span> dataSource<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span> <span class="token string">"pg_publication"</span><span class="token punctuation">,</span> <span class="token string">"pubname = $1"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span> slotName <span class="token punctuation">}</span><span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">async</span> <span class="token return-type class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> <span class="token function">ReadExistingEventsFromSnapshot</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> snapshotName<span class="token punctuation">,</span> <span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">EnumeratorCancellation</span></span><span class="token punctuation">]</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> connection <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NpgsqlConnection</span><span class="token punctuation">(</span>options<span class="token punctuation">.</span>ConnectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> connection<span class="token punctuation">.</span><span class="token function">OpenAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> connection<span class="token punctuation">.</span><span class="token function">GetEventsFromSnapshot</span><span class="token punctuation">(</span> snapshotName<span class="token punctuation">,</span> options<span class="token punctuation">.</span>TableName<span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">internal</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">CreateReplicationSlotResult</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AlreadyExists</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CreateReplicationSlotResult</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Created</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> TableName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> SnapshotName<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">CreateReplicationSlotResult</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>It is still a naive implementation as it doesn’t have full fault tolerance for reading snapshotted data. The logical replication will ensure checkpointing on its own, we don’t need to take care of that, yet for snapshotted data, that’s another story. But that’s a story for another dedicated article!</p> <p>See also more technical details around implementation in <a href="https://github.com/oskardudycz/PostgresOutboxPatternWithCDC.NET/pull/2">Pull Request</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Big thanks go to <a href="https://github.com/Brar">Brar Piening</a> for implementing that part natively in <a href="https://www.npgsql.org/">Npgsql</a> and pointing me in the right direction with patient explanations.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How events can help in making the state-based approach efficient]]>https://event-driven.io/en/how_events_can_help_on_making_state_based_approach_efficient/https://event-driven.io/en/how_events_can_help_on_making_state_based_approach_efficient/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b5d3471b91ede8b299343b5f06a16a98/8299d/2022-11-13-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAEEAv/EABUBAQEAAAAAAAAAAAAAAAAAAAID/9oADAMBAAIQAxAAAAGVLZcgg2//xAAZEAADAQEBAAAAAAAAAAAAAAAAAQISAxH/2gAIAQEAAQUCSwOdDlF9Gx15B//EABgRAAIDAAAAAAAAAAAAAAAAAAABAhET/9oACAEDAQE/AUrM4n//xAAWEQEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQIBAT8Bra//xAAXEAEAAwAAAAAAAAAAAAAAAAAQARFR/9oACAEBAAY/Apax/8QAGhAAAwEAAwAAAAAAAAAAAAAAAAERITFRgf/aAAgBAQABPyFukbXAlL6UjyFIHZdCeH//2gAMAwEAAgADAAAAEEA//8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARIf/aAAgBAwEBPxDN2EBy/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQARIf/aAAgBAgEBPxBY8mv/xAAbEAEBAQACAwAAAAAAAAAAAAABEQAhMUFRYf/aAAgBAQABPxAogar2YiSUFCzC6L96wEJrTyuHKTpwyzKh3//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b5d3471b91ede8b299343b5f06a16a98/c60e9/2022-11-13-cover.jpg" srcset="/static/b5d3471b91ede8b299343b5f06a16a98/37402/2022-11-13-cover.jpg 200w, /static/b5d3471b91ede8b299343b5f06a16a98/4cda9/2022-11-13-cover.jpg 400w, /static/b5d3471b91ede8b299343b5f06a16a98/c60e9/2022-11-13-cover.jpg 800w, /static/b5d3471b91ede8b299343b5f06a16a98/6c738/2022-11-13-cover.jpg 1200w, /static/b5d3471b91ede8b299343b5f06a16a98/56dca/2022-11-13-cover.jpg 1600w, /static/b5d3471b91ede8b299343b5f06a16a98/8299d/2022-11-13-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In my last few articles, I showed the power of <a href="/en/writing_and_testing_business_logic_in_fsharp/">functional composition</a>. That’s a foundation for <a href="/en/type_script_node_Js_event_sourcing/">effective modelling of your business logic</a>. It allows for having more straightforward and explicit code. We discussed starting from the classical Domain Driven Design and <a href="/en/slim_your_entities_with_event_sourcing/">sliming all the abstractions we could</a>. I sprinkled that with a bit of Event Sourcing because I like and believe in the usefulness of this pattern. Yet, the same approach can also be applied to the classical state-based approach. Let’s say that we have the following code representing the business logic of adding a product item to the shopping cart:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AddProductItemToShoppingCartHandler</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span> <span class="token keyword">private</span> repository<span class="token operator">:</span> MongoDbRepository<span class="token operator">&lt;</span>ShoppingCartModel<span class="token operator">></span><span class="token punctuation">,</span> <span class="token keyword">private</span> mapper<span class="token operator">:</span> ShoppingCartMapper<span class="token punctuation">,</span> <span class="token keyword">private</span> eventBus<span class="token operator">:</span> EventBus <span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">handle</span><span class="token punctuation">(</span>command<span class="token operator">:</span> AddProductItemToShoppingCart<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> model <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>repository<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>model <span class="token operator">===</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">Shopping cart with id </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>command<span class="token punctuation">.</span>shoppingCartId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> not found!</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> aggregate <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mapper<span class="token punctuation">.</span><span class="token function">toAggregate</span><span class="token punctuation">(</span>model<span class="token punctuation">)</span><span class="token punctuation">;</span> aggregate<span class="token punctuation">.</span><span class="token function">addProductItem</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>repository<span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>mapper<span class="token punctuation">.</span><span class="token function">toModel</span><span class="token punctuation">(</span>aggregate<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> event <span class="token keyword">of</span> aggregate<span class="token punctuation">.</span><span class="token function">dequeueUncomittedEvents</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventBus<span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s a classical way of handling code in the Object-Oriented, Domain-Driven Design style. We have a storage model that’s mapped to aggregate. We run some logic on the aggregate that changes the state and produces the event. We can use this event to notify subscribers about the action they need to take (e.g. update inventory state, publish notification, update read model etc.).</p> <p>The mapper can look as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartMapper</span> <span class="token punctuation">{</span> <span class="token function">toModel</span><span class="token punctuation">(</span>aggregate<span class="token operator">:</span> ShoppingCart<span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartModel <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCartModel</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>aggregate<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">,</span> aggregate<span class="token punctuation">.</span>customerId<span class="token punctuation">,</span> aggregate<span class="token punctuation">.</span>status<span class="token punctuation">,</span> aggregate<span class="token punctuation">.</span>productItems<span class="token punctuation">,</span> aggregate<span class="token punctuation">.</span>openedAt<span class="token punctuation">,</span> aggregate<span class="token punctuation">.</span>confirmedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">toAggregate</span><span class="token punctuation">(</span>model<span class="token operator">:</span> ShoppingCartModel<span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCart <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">(</span> model<span class="token punctuation">.</span>_id<span class="token punctuation">.</span><span class="token function">toHexString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> model<span class="token punctuation">.</span>customerId<span class="token punctuation">,</span> model<span class="token punctuation">.</span>status<span class="token punctuation">,</span> model<span class="token punctuation">.</span>productItems<span class="token punctuation">,</span> model<span class="token punctuation">.</span>openedAt<span class="token punctuation">,</span> model<span class="token punctuation">.</span>confirmedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>If we’d like to store it in MongoDB, then our repository class could look like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">class</span> <span class="token class-name">MongoDbRepository<span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token keyword">extends</span> Document <span class="token operator">&amp;</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> ObjectId <span class="token punctuation">}</span><span class="token operator">></span></span> <span class="token punctuation">{</span> <span class="token keyword">protected</span> collection<span class="token operator">:</span> Collection<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token function">constructor</span><span class="token punctuation">(</span> mongo<span class="token operator">:</span> MongoClient<span class="token punctuation">,</span> collectionName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> databaseName<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">|</span> <span class="token keyword">undefined</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collection <span class="token operator">=</span> <span class="token generic-function"><span class="token function">getCollection</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span>mongo<span class="token punctuation">,</span> collectionName<span class="token punctuation">,</span> databaseName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">add</span><span class="token punctuation">(</span>entity<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collection<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> entity<span class="token punctuation">.</span>_id <span class="token punctuation">}</span> <span class="token keyword">as</span> Filter<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> entity <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">update</span><span class="token punctuation">(</span>entity<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collection<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> entity<span class="token punctuation">.</span>_id <span class="token punctuation">}</span> <span class="token keyword">as</span> Filter<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> entity <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">upsert</span><span class="token punctuation">(</span>entity<span class="token operator">:</span> <span class="token constant">T</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collection<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> entity<span class="token punctuation">.</span>_id <span class="token punctuation">}</span> <span class="token keyword">as</span> Filter<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> entity <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">async</span> <span class="token function">find</span><span class="token punctuation">(</span>id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">this</span><span class="token punctuation">.</span>collection<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">as</span> Filter<span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token operator">===</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token keyword">return</span> result <span class="token keyword">as</span> <span class="token constant">T</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Some may say that’s too many abstractions, and I could <em>yessir</em> to that. Still, the worst part is that <strong>we’re doing the lowest common denominator by not benefiting from the storage capabilities</strong>.</p> <p>MongoDB has a sweet set of atomic operations you can apply to the document. You can update a subset of properties, increment values, and nested arrays without modifying the whole document. This is not so common for <a href="/en/key-value-stores/">key-value databases</a>. It’s fine to keep documents a bit bigger, denormalised and use the same document for write and read models by just querying for a subset of information. We’re not getting a big penalty hit if we’re doing atomic operations. Having that, loading and updating the whole document each time is overkill.</p> <p>Let’s say that we <a href="/en/slim_your_entities_with_event_sourcing/">applied transformations explained in the aforementioned article</a> and ended up with a similar model to the one presented in <a href="/en/type_script_node_Js_event_sourcing/">Straightforward Event Sourcing with TypeScript and NodeJS</a>.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">type</span> <span class="token class-name">ShoppingCart</span> <span class="token operator">=</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> status<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> Map<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">number</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> ShoppingCartStatus <span class="token operator">=</span> <span class="token punctuation">{</span> Opened<span class="token operator">:</span> <span class="token string">'Opened'</span><span class="token punctuation">,</span> Confirmed<span class="token operator">:</span> <span class="token string">'Confirmed'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ProductItem</span> <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartOpened</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'shopping-cart-opened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> customerId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemAddedToShoppingCart</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'product-item-added-to-shopping-cart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> addedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'product-item-removed-from-shopping-cart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> removedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartConfirmed</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'shopping-cart-confirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> ShoppingCartOpened <span class="token operator">|</span> ProductItemAddedToShoppingCart <span class="token operator">|</span> ProductItemRemovedFromShoppingCart <span class="token operator">|</span> ShoppingCartConfirmed<span class="token punctuation">;</span></code></pre></div> <p>And the logic:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> openShoppingCart <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> OpenShoppingCart <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartOpened <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'shopping-cart-opened'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> command<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> customerId<span class="token operator">:</span> command<span class="token punctuation">.</span>data<span class="token punctuation">.</span>customerId<span class="token punctuation">,</span> openedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> addProductItemToShoppingCart <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> AddProductItemToShoppingCart<span class="token punctuation">,</span> cart<span class="token operator">:</span> ShoppingCart <span class="token punctuation">)</span><span class="token operator">:</span> ProductItemAddedToShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cart<span class="token punctuation">.</span>status <span class="token operator">!==</span> ShoppingCartStatus<span class="token punctuation">.</span>Opened<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'Cannot add product to not opened shopping cart'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'product-item-added-to-shopping-cart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> cart<span class="token punctuation">.</span>id<span class="token punctuation">,</span> productItem<span class="token operator">:</span> command<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> addedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> removeProductItemFromShoppingCart <span class="token operator">=</span> <span class="token punctuation">(</span> command<span class="token operator">:</span> RemoveProductItemFromShoppingCart<span class="token punctuation">,</span> cart<span class="token operator">:</span> ShoppingCart <span class="token punctuation">)</span><span class="token operator">:</span> ProductItemRemovedFromShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cart<span class="token punctuation">.</span>status <span class="token operator">!==</span> ShoppingCartStatus<span class="token punctuation">.</span>Opened<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'Cannot remove product from not opened shopping cart'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> productItemToRemove <span class="token operator">=</span> command<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity <span class="token punctuation">}</span> <span class="token operator">=</span> productItemToRemove<span class="token punctuation">;</span> <span class="token keyword">const</span> currentQuantity <span class="token operator">=</span> cart<span class="token punctuation">.</span>productItems<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newQuantity <span class="token operator">=</span> <span class="token punctuation">(</span>currentQuantity <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">-</span> quantity<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>newQuantity <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">Error</span><span class="token punctuation">(</span><span class="token string">'Product Item not found'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'product-item-removed-from-shopping-cart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> cart<span class="token punctuation">.</span>id<span class="token punctuation">,</span> productItem<span class="token operator">:</span> productItemToRemove<span class="token punctuation">,</span> removedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">const</span> confirmShoppingCart <span class="token operator">=</span> <span class="token punctuation">(</span> _command<span class="token operator">:</span> ConfirmShoppingCart<span class="token punctuation">,</span> cart<span class="token operator">:</span> ShoppingCart <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartConfirmed <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cart<span class="token punctuation">.</span>status <span class="token operator">!==</span> ShoppingCartStatus<span class="token punctuation">.</span>Opened<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token function">Error</span><span class="token punctuation">(</span><span class="token string">'Cannot confirm to not opened shopping cart'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'shopping-cart-confirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> cart<span class="token punctuation">.</span>id<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>The pattern is simple, handlers take command, current state. They run business logic and either return an event as a result or throw an error.</strong> That’s straightforward and extremely easy to test way. Still, it looks unusual, especially if we don’t intend to do Event Sourcing but use a state-based approach with MongoDB. Yet!</p> <p>Events are facts representing the result of our business logic. They gather all the information about what has changed. <a href="/en/events_should_be_as_small_as_possible/">They should be as granular as possible</a> and have all the information about the change inside. We could also benefit from that in our state-based approach!</p> <p><strong>What if we used events as the input of the update state method?</strong> How come? Let’s say that we have a following data model:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartModel</span> <span class="token operator">=</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> ObjectId<span class="token punctuation">;</span> customerId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> status<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p><strong>To update it based on the results of our business logic (so events) we could write a following method:</strong></p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> store <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> carts<span class="token operator">:</span> Collection<span class="token operator">&lt;</span>ShoppingCartModel<span class="token operator">></span><span class="token punctuation">,</span> event<span class="token operator">:</span> ShoppingCartEvent <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'shopping-cart-opened'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> customerId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>customerId<span class="token punctuation">,</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Opened<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> openedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>openedAt<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'product-item-added-to-shopping-cart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string-property property">'productItems.productId'</span><span class="token operator">:</span> <span class="token punctuation">{</span> $ne<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $addToSet<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'productItems.$[orderItem].quantity'</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> arrayFilters<span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> <span class="token string-property property">'orderItem.productId'</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">,</span> upsert<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'product-item-removed-from-shopping-cart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string-property property">'productItems.productId'</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'productItems.$.quantity'</span><span class="token operator">:</span> <span class="token operator">-</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'shopping-cart-confirmed'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>confirmedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>It’s a simple method that takes MongoDB documents collection and the event. As I mentioned, we assume that we have all information we need for the event. If we miss something, we should consider updating the event payload. As our events represent the specific business operation, they do not just have the latest state; we can precisely modify the document. We’re doing pattern matching based on the event type to make a particular change.</p> <p>For instance, the removal of the product item from the shopping cart:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">case</span> <span class="token string">'product-item-removed-from-shopping-cart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> carts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> _id<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">ObjectId</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string-property property">'productItems.productId'</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $inc<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token string-property property">'productItems.$.quantity'</span><span class="token operator">:</span> <span class="token operator">-</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re saying that for the shopping cart of a particular id, we’re removing the product item with the specific id. MongoDB, we’ll be smart enough to give us the exact product item, so we can specify how to decrement the quantity.</p> <p>We can then use this method in the handler in the following way:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">removeProductItemFromShoppingCartRoute</span> <span class="token operator">=</span> <span class="token punctuation">(</span> carts<span class="token operator">:</span> Collection<span class="token operator">&lt;</span>ShoppingCartModel<span class="token operator">></span><span class="token punctuation">,</span> eventBus<span class="token operator">:</span> EventBus<span class="token punctuation">,</span> router<span class="token operator">:</span> Router <span class="token punctuation">)</span> <span class="token operator">=></span> router<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span> <span class="token string">'/customers/:customerId/shopping-carts/:shoppingCartId/product-items'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> request<span class="token operator">:</span> RemoveProductItemFromShoppingCartRequest<span class="token punctuation">,</span> response<span class="token operator">:</span> Response<span class="token punctuation">,</span> next<span class="token operator">:</span> NextFunction <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> command <span class="token operator">=</span> <span class="token function">from</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> cart <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getShoppingCart</span><span class="token punctuation">(</span>carts<span class="token punctuation">,</span> command<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> event <span class="token operator">=</span> <span class="token function">removeProductItemFromShoppingCart</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> cart<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">store</span><span class="token punctuation">(</span>carts<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> eventBus<span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">sendStatus</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">next</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>If we didn’t have precise information from events, then to have efficient updates, we either had to:</p> <ul> <li>couple the storage with business logic,</li> <li>do sophisticated diff mechanisms between the old and new states,</li> <li>stay with non-performant upserts.</li> </ul> <p><strong>As you see, events are helpful not only for event-sourced storage.</strong> They can also hugely simplify regular storage and make it even more efficient. That’s the benefit of having precise business information instead of <em>one change to rule them all</em>. By thinking upfront and working on our model, we can improve not only the business logic but also the technical implementation. All in all, it’s about the pragmatism of focusing on what we want to achieve. Using events can create a good synergy between the domain and technical concepts.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Never Lose Data Again - Event Sourcing to the Rescue!]]>https://event-driven.io/en/never_lose_data_with_event_sourcing/https://event-driven.io/en/never_lose_data_with_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/08436d286b1afb2c951becc9df73db6e/8299d/2022-11-06-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAMEAQX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAABazlXRhOL/8QAGxAAAQQDAAAAAAAAAAAAAAAAAgEDEjIAESP/2gAIAQEAAQUCU+bS6aOMgJcCp2//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/AT//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/AT//xAAaEAACAgMAAAAAAAAAAAAAAAABAhAxAAMh/9oACAEBAAY/AiNd4A7dmo//xAAaEAEAAgMBAAAAAAAAAAAAAAABABEhMUFx/9oACAEBAAE/IbcjreoVwSu0MobexAA9WzGP/9oADAMBAAIAAwAAABD8z//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAwEBPxCMf//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAgEBPxCtf//EABsQAQEBAAMBAQAAAAAAAAAAAAERACFBYVGx/9oACAEBAAE/EIf4/BTr3QqVWqoPV1SFHPzETxSfmv0F5WV+4UChN//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/08436d286b1afb2c951becc9df73db6e/c60e9/2022-11-06-cover.jpg" srcset="/static/08436d286b1afb2c951becc9df73db6e/37402/2022-11-06-cover.jpg 200w, /static/08436d286b1afb2c951becc9df73db6e/4cda9/2022-11-06-cover.jpg 400w, /static/08436d286b1afb2c951becc9df73db6e/c60e9/2022-11-06-cover.jpg 800w, /static/08436d286b1afb2c951becc9df73db6e/6c738/2022-11-06-cover.jpg 1200w, /static/08436d286b1afb2c951becc9df73db6e/56dca/2022-11-06-cover.jpg 1600w, /static/08436d286b1afb2c951becc9df73db6e/8299d/2022-11-06-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Relational databases are not losing data. They’re robust, consistent and secure! Are they?</strong></p> <p>Too often, we mix technical and logical concepts. Business transactions are not the same as database transactions. The fact that we can safely assume that information that we put into the database will be stored durable doesn’t mean that it won’t be lost.</p> <p><strong>We don’t even need to delete any data to lose precious business information.</strong> Each override replacing the previous state with the new one may already erase the valuable information. For instance, seeing that the shopping cart is empty, how will you know that it’s just a new shopping cart or someone added and removed a product? Such “pessimistic” scenarios are sometimes more valuable insight for the business than just knowing that everything went right and someone bought something eventually. In this example, knowing that someone put the product into the shopping cart but decided not to buy it can give us information about some gap or even an opportunity to “close the deal”. Yet, we need to have that information!</p> <p>During my studies, I spent the first three summer breaks working as a shop assistant in a clothes shop. The most important trick I learned was to persuade customers to try clothes. After that, the chance of buying grew a lot. The same rule applies to e-commerce. The rate of abandoned carts is between 60-95%. That’s a lot. Those customers who had the intention to buy it and were so close to doing it but at the last phase resigned. If we store details of our business process, we can optimise and improve it. What’s more, targeting and focusing on the people we know are interested in is much more effective than trying to reach everyone. Such precision is much more cost-effective, and thus our business can benefit from it.</p> <p><strong>Event Sourcing, contrary to the standard approach, keeps all the facts that happened in our system.</strong> They’re stored as business events. We can look back and make analyses and enhanced diagnostics.</p> <p>Events are a great source and input for future workflows: analytics, reporting or even machine learning. For instance, we can create a recommendation engine by knowing what products were bought together. We can also do various reporting and forecasting, e.g. having the number of sales as of the last month, we can see how it could look on the new pricing model. Such knowledge is critical in budgeting and process optimisations.</p> <p><strong>Nowadays, storage is cheap, but the information is priceless.</strong> We should take benefit of it. Keeping the business facts is much closer to how the real world works. In the real world, there’s no such thing as deletion. If we throw something into the trash bin, it doesn’t magically disappear. It’s just moved from one place to another.</p> <p><strong>It’s also worth noting that Event Sourcing is pretty often conflated with Event Streaming.</strong> Both names sound similar and are about events, but understanding the difference is essential. Event Sourcing is a storage pattern; it’s focused on capturing business facts and durably storing them. Event stores are databases and guarantee atomic writes, reading your own writes, and optimistic concurrency. So everything you’d expect from databases. Event Streaming is about data in motion. Tooling is focused on moving data from one place to another. This is the place where queues shine. Some of them have durability features, but they don’t provide a guarantee to make your decision based on the latest states. Read more in my other article <a href="/en/event_streaming_is_not_event_sourcing/">Event Streaming is not Event Sourcing!</a>.</p> <p><strong><a href="https://www.infoq.com/articles/architecture-trends-2022/">InfoQ in their report</a> shows Event Sourcing is in the “Late Majority” adoption phase.</strong> That means that it’s an established, common technique. Indeed it’s getting much more traction. Yet, in my opinion, it’s still a niche. That’s a chance for you as it means that you can get a competitive advantage by using it. You can do better than other companies, move fast and innovate. Using an event-driven approach <a href="/en/how_using_events_help_in_teams_autonomy/">can also help you build autonomous services and teams</a>. That can help in making your development process even more efficient. Don’t wait to jump on the bandwagon; some empty seats are still left!</p> <p><strong>If you don’t know how to start, don’t be shy to contact me.</strong> I’m here to help. Check my <a href="/en/training/">training</a> page. A workshop is the most effective way to jump-start. Try also my <a href="/en/introduction_to_event_sourcing/">Introduction to Event Sourcing - Self-Paced Kit</a>. When not to use Event Sourcing? I got you covered; I explained that in <a href="/en/when_not_to_use_event_sourcing/">my other article here</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How Postgres sequences issues can impact your messaging guarantees]]>https://event-driven.io/en/ordering_in_postgres_outbox/https://event-driven.io/en/ordering_in_postgres_outbox/<p><strong>Big picture descriptions and overall explanations are great. They help us to understand the foundations of new ideas and then find the place we need to evaluate more.</strong> Still, we’re starting to face reality when we try to go deeper. This may be harsh. It may be a good bullshit filter when we realise the idea is not as simple as pictured. We may just throw, “this can’t be done!” or try to find our way through the problems.</p> <p>Let’s take an example of the Outbox pattern I described in my two articles:</p> <ul> <li><a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox, Inbox patterns and delivery guarantees explained</a>,</li> <li><a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">Push-based Outbox Pattern with Postgres Logical Replication</a>.</li> </ul> <p>Two articles should be enough to exhaust the topic, aye? Let’s go deeper and discuss the potential poll-based implementation on top of Postgres. As it’s the most flexible relational database nowadays, it cannot be that hard.</p> <p><strong>Let’s say that we’ll use the table structure from <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication/">the previous article</a>:</strong></p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> outbox<span class="token punctuation">(</span> <span class="token comment">-- the autoincremented position of the message to respect the order</span> position BIGSERIAL <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token comment">-- this may allow you to partition publications, e.g. per tenant</span> publication_id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- unique message id, which can be used for deduplication or idempotency</span> message_id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- the message type, e.g. `TransactionRecorded`</span> message_type <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- serialised message data, e.g. to JSON</span> <span class="token keyword">data</span> JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- diagnostic information of when the message was scheduled</span> scheduled <span class="token keyword">TIMESTAMP</span> <span class="token keyword">WITH</span> <span class="token keyword">TIME</span> ZONE <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">default</span> <span class="token punctuation">(</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’ll be storing messages to publish there and then continuously polling those messages in the background process using the following query:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> position<span class="token punctuation">,</span> message_id<span class="token punctuation">,</span> message_type<span class="token punctuation">,</span> <span class="token keyword">data</span> <span class="token keyword">FROM</span> outbox <span class="token keyword">WHERE</span> position <span class="token operator">></span> last_processed_position <span class="token keyword">ORDER</span> <span class="token keyword">BY</span> position <span class="token keyword">ASC</span> <span class="token keyword">LIMIT</span> <span class="token number">100</span><span class="token punctuation">;</span></code></pre></div> <p>The last processed position will be taken from the subscriptions table:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> subscribers <span class="token punctuation">(</span> <span class="token comment">-- subscription name</span> subscription_id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token comment">-- information about the position of the last processed message</span> last_processed_position <span class="token keyword">INTEGER</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- inform which publication we're subscribing to</span> publication_id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>After we process all the messages from the read batch, we update the last processed position for the particular subscriber. So far, so good, easy peasy.</p> <p>We’re using the <em>BIGSERIAL</em> type to generate a monotonic, autoincremented position. The happy path for the three parallel transactions will look as follows:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/fb7da2ffe598ae96f207f11aa7cf6008/a331c/2022-10-28-01.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACB0lEQVQozz2SyYrWQACE84g+xzyJ4GFOIl48iiLiVWH4BYcZcdZ/X5J00p3el/Sazj+jh5EMKNSlijrVV4UNWfaBa8+1F8r2LtqQtU1TYoIw0YbRpwcXRq49015oL03QNmo3FNJE40dlB2EiD3+kf5QmMOW5DrJPXAeuvHFDRxWXmkmzrxEVWilDuC4glstNWTakRXT++eVm9r7BqkUMcwMQk31SfZJ9BJD6EBnprn98k5yFENuOFYio9a5uiapafPnx1e3Zp5oPFWSHGm0PDeGm98dfu+vZ1ffZ+d3XD69vTl/cX5zN1/V2Dwo/PFLZLzcHgKhKTxTB+bsTcHvGbRbaIaK0HXx6gFhUDVmutxAATGXbsarBhfGZcH2oIeaqxXy9XJx/frNdLRoRqqYrAWLK2nhsOr7a1beLbYtFBclmV+8rWDDltcs1pJdX94caEWFZfJp/Of359gRR1TFTAixMrBqy2FRXd6uqwVVLd2W7r1AhbdJ2mOaBFBFJuEZEHu4u9tczSESDBcTS+BEgvtpWN/drgGjdkn3ZlqArJhg6CB2VTcJEaaIwQflH6X9PYCdmUfaJCNsgDiCDWBJuMFVUukK7wfjB+GzcoN2g7DDhmT4Qe5+Nz/o5Nz73YXTpaMIIuMVMU2ELn8aQRp/yf7mU/ZDDPxvSMQzPnZh9zC5mKHouterdX6rJS/DYsjSzAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="01" title="01" src="/static/fb7da2ffe598ae96f207f11aa7cf6008/a331c/2022-10-28-01.png" srcset="/static/fb7da2ffe598ae96f207f11aa7cf6008/36ca5/2022-10-28-01.png 200w, /static/fb7da2ffe598ae96f207f11aa7cf6008/a3397/2022-10-28-01.png 400w, /static/fb7da2ffe598ae96f207f11aa7cf6008/a331c/2022-10-28-01.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Once transactions are committed, the results should look like this:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d80bcd96b1534b2796a55e5b349d8e61/a331c/2022-10-28-02.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACD0lEQVQozy2SS2sTYRSG528WXAnu+kvc6EKkIggWBBFB2oIiUmvVtrZJmpKk02lmJpnLd79fJ40uKtMWnsXh5T1n85xE2UilxdxgbghTUjtlI1euT4QlwinbGX+jXIfuO9xQYblyXIeECi9Mx2QgwhH7j9k1lRYxjbml0mPe3xU6NJBhwhEVWVFDwhkTAPOkaulkNl8i3FI623ma/XxfU9ESgrhsKWXKU+mpdIsKaeNQWw9+fKYYGeuXDUpqyKZpAZhoCB7uPpsef2rtTUNZhWAJW8SVNKvTq8H3wcHB7/OvH14NX2yMT/YvZkWalYkJa0jkJJ3XhIjVLQL15O1mdbFPbUelgUwIHbS/qVsyva7Pvn28fPNkkY4qyPNFkwgTAebzssJStozlxeXpl9dFMYM21gTVGBGppV1VDToezg733g23Hl+OjtO8yebLBDHDdSwqeDIYlwAioWm8ne49P32zCQgHVC4gItIVS/DnPDvc2R6/3EhHR1kJs3yZMOW5CgCLsoaAcSwkYLycneTjA0B5e4cw3bIhv84mh7vbF1uP0tHR9QJeF3XSy2C9T6Y8EY722F6Y/Yu5Qcxg7qj0gKhFQ/KiyrOrFpAWcUhVwrUXJvTowE24d8P6H3DSRGFin+sgTJS2034t/brE6mHZ+M76zvh4Rz9oH02I9iGJ1q9suOu4aFxQNlREYsqZ1P8BJ3BImPD/rQkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="02" title="02" src="/static/d80bcd96b1534b2796a55e5b349d8e61/a331c/2022-10-28-02.png" srcset="/static/d80bcd96b1534b2796a55e5b349d8e61/36ca5/2022-10-28-02.png 200w, /static/d80bcd96b1534b2796a55e5b349d8e61/a3397/2022-10-28-02.png 400w, /static/d80bcd96b1534b2796a55e5b349d8e61/a331c/2022-10-28-02.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Yet, since I’m writing this article, you can guess that something may be odd here.</strong> What if the last transaction was the fastest and committed as the first one.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0dbf3d9fd5742f835bf8f628a94fe7a8/a331c/2022-10-28-03.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACAklEQVQoz01Ry2rcQBDUd5p8SC655gdCDskl4FMe4GMeGD8CIY5REnu9u7IsrVbSSpqZ7ul5a7Q2hBA2SyBQh+qiqIaqRLsIynPpuLRAVtuobEAyHC0nJ8hrN9lwr/3EpONoxc7mpPLSjAlQIDuBHgU5hlaQRfsA/hcnByrsQ8mMmwE5EAeVr9pBICL1XCZNB7NlOfQdAhdCCHLri6Ps3RPggxBMKg8qgPLrlhnrWdem5++BM+tCvWHJZsB5tkIA4L0YWkCoLo7mR09RSgABSMpuL2/T0/Ts7MvVp7cvfzx/dHVxer0os7sqseNDL/Q8KwTnREpKSW4y4SF/83h99grJkvYm3Le9uM7qy4+vly8OqkXa9FRWm0TZ2HFZVA1JBFKcdM+6YWhvTg6rnyeSSGqv3FR3PJ1lX8+Pb44P82yZ3dV5UScMrTSxXHeXs2L++fW3ZwdtdTdIt6HQ0X4Fh3os1v0iW6WzvOxt2fDbYp2XTYJ6lHrsuKw7rFeLIv3QsYGR6QUwqVCNqINyU9Xy+bL8fjWv6s2q7vOiLlZtssvG3Z6oPZgtjb9RB0G7h0J6hjsCKvRCr1teNazpoOfUDTiASaQZye4RpQlIFlTYi8pGspHM+PeMelfklty24rpjchA6sWGyIf6HyYRox+j+KS5s3Ti5MFkfrY/Gx0YoARKV+QOv5k01M9Oi0wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="03" title="03" src="/static/0dbf3d9fd5742f835bf8f628a94fe7a8/a331c/2022-10-28-03.png" srcset="/static/0dbf3d9fd5742f835bf8f628a94fe7a8/36ca5/2022-10-28-03.png 200w, /static/0dbf3d9fd5742f835bf8f628a94fe7a8/a3397/2022-10-28-03.png 400w, /static/0dbf3d9fd5742f835bf8f628a94fe7a8/a331c/2022-10-28-03.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>If the rest followed the order, we’d see that the order in which records were appended to the table may differ from the order of positions.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2bcfb7c06b7af21750d775d6c52cf277/a331c/2022-10-28-04.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACB0lEQVQozzWRS2sUURCF+29G3Ik/wpV7t4qYjYgP8LUIERQR8yKJiZ0xk0zS6Z7pnn7e96376tudSRCR6VE4i1OnqCqoL1C2Y9ISYYgwVBowHeiWC024IcJS6ZTtTbtQrsfCYm6oMExaoZzQPmDSSdNz5akcetJwu2DuN5GWQUvEcq/UvkKcMIEZxGmFqOBcNlgERc3OL6c5wg3jS8l2+mPj7MNDREhFKVeOQcvAZQXWxuG6DHc+M4KNbfMKByXikyhtmKgoLTAqOVwebYYbjyqpCw5YAJjr46twK9ze3v/19f36yZO108Pv44s0irPA+BtE4TyaVoxR0EwCswvhby/ePUh3XjTghHK6XZQNPbsqjr99jJ7fn0ejAonZvAqk6RoikqwgAEjqEkyOcYnq0dbr+HQbgxbKge2LmhyGk91Pb07W712ODqNpFU/zAHMjdJfm9fFZMtl9e/T4TpElSNpKulq64WGWKz+b1z/Hyd7mq/HTtWh0EGconhUBV14oXxOR16xIL5LwS40RlrqhDAvgynPVgunziuyHk73Nl+Nnd6PRQZKhJC0Dwi3hS54cHNPXwv9hA7bVTbyiDW1D1byis7SYxVHd0BoLxFQgtJfGS9NJ0wnd8gHvKoQhlNoPZQe21+0NtLcZ0f+GTdv/V7cy2i2N/Z/btrd+KF1vXKesLygQJjmYv4w1SQuGVERlAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="04" title="04" src="/static/2bcfb7c06b7af21750d775d6c52cf277/a331c/2022-10-28-04.png" srcset="/static/2bcfb7c06b7af21750d775d6c52cf277/36ca5/2022-10-28-04.png 200w, /static/2bcfb7c06b7af21750d775d6c52cf277/a3397/2022-10-28-04.png 400w, /static/2bcfb7c06b7af21750d775d6c52cf277/a331c/2022-10-28-04.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>This is possible because of how <a href="https://www.postgresql.org/docs/current/sql-createsequence.html">Postgres sequences</a> work. They’re evaluated before the transaction commit.</strong> _“But we’re using BIGSERIAL here!“. It doesn’t matter. BIGSERIAL is just a syntactic sugar for the autogenerated Postgres sequence. It’s just using it behind the scenes.</p> <p>The other important thing is that number of sequences is not reused. This is a valid approach to avoid having an accidental clash of numbers between transactions. The number is lost if the transaction fails and is rolled back. <strong>In our case, this may result in gaps in position numbering.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0197066805884fbeeba447676d6d6179/a331c/2022-10-28-06.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACEUlEQVQozz2RO2sUYRiF52/aWFpo5b8QLGxE1CKFiBA0CIKCGDYhmAQ3m2Sz19ndmdnvm+9+v81MEi0iu0HhFO857+E0T2Z9K0xgyjPlubTaRRNaZdPGqsB1tKHz6caGjt53lBc6KBuVbTKho/adtA3XkYc/0t8KHaj0TAVpElebXe2amkgmFBMmL2rClZQaU5WtkRhNl8sKV5AM955Neh9KJCtIIdMFpNwkaZIwsYTUhUgRODv8Jhj1Ia5rkkEiJvMCYLleo8Hu89H+x5qlGtCqgKvlmnNj/PWv+aDXPzj4efl99+3g5YPLk95wUszyMvPNLRFmPF1wSFy6ExDMdh7j831jW6OcItLY5NINRHy8gP0fn6Y7j6rpBcBqVdaZ9i1ialkAzSRFrBpdne+9LsdXjAdc1aiETFoTrwFix4Px4Zf3Z68eTs+PZ6s6X1YZlV65tgDkpH+5KCDmlsW74ecXp2+e1ETWVC9LxHUsADm9mB193R29ezof9vMS5at1JmyjbIOYKQGBWGCmIOaLi+P8rAcwr2oOsNC+qyCbzIvB1bwkpoA0X1TLos429OSGp7SJ6yi22gDzv5nyW2ZRmIS5rSArAQE1xVQhIqlwmXKN9o32rXaNco20jTBJ2qhcNL79n2vfmtC5dK1DVzKLqCLcZj52IXV+o9b/O0LahrG7/4Zma0PnY+tiC7hlQkvt/wLHTUirfDnbQAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="06" title="06" src="/static/0197066805884fbeeba447676d6d6179/a331c/2022-10-28-06.png" srcset="/static/0197066805884fbeeba447676d6d6179/36ca5/2022-10-28-06.png 200w, /static/0197066805884fbeeba447676d6d6179/a3397/2022-10-28-06.png 400w, /static/0197066805884fbeeba447676d6d6179/a331c/2022-10-28-06.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>So right now, we have two issues:</strong></p> <ul> <li>we may have messages out of order,</li> <li>we may have gaps in ordering.</li> </ul> <p>I already wrote that positions <a href="https://event-driven.io/en/lets_talk_about_positions_in_event_stores/">are pretty important for Event Stores</a>. Yet, even using an outbox to broadcast messages may have severe consequences.</p> <p>Let’s get back to the race condition example.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/79c37854f26c19eaa14f7d3ddb294216/a331c/2022-10-28-07.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACFklEQVQozyWMy2oUQRRA+zdFP8SNa/eCC0EQsokQyFIkMRoRkwwTTebV0+9XdT1u1a2uqq7unpmQhcRszuJwOEHnRtA9V44rC+i0HbX1Eg2XVigH2Hdusn7fuYlJ+18+ZarrlRkCQI92kt0g0DFpBVrp9uAeOTrQ/nmKZmio5EJx0HHWUKFAYstUUBFYbFLaEglcCCHQFden4ckbwakQTOketBfYZxU1tmeknl9+Ac6s82XDgprKVZhJAOCtoDVIyK5PV6dvlVIAAqSW3WD9Q03g/Ofs7OTj7ftXd1ff79dpGOWBHQ5U6FUYC84QtVKIbtf5w/bz6+LHJ4nG2GFdRbPlfB2Tm6/H4YeXxXpeUUzzJkA7Eq6SvEKUgJpj17KW0np5cZT/vVCIaHzJ2TqJbhfR78vz5dnRNtyEcRklZcCkVWZMCzJbJMvL4+t3L6o8oso1qifKcWmZtNrt84qvwmy+iNLWphXfJmWU1oHsBtUNhKmyVbRJirtvTHAwjisEbbWd0E7opqxmqzC9vVvlZZOVbZSUSd4EXDkmHaBvhb7fZFEFRSOSoo3zJs5JUpCsolw5CqaoeV7xqoWWIaGSgQmUGdAMXT8Rrn5dzeZ/FsswWW3TxSZebuIoq7KyFei0m7SbjN+hm3JuCFNUdIHtJ9tPxo1u2E37x2H30I87Px78+NA/ce/Hg/O758a6sevHknccUKL9ByqESZ22CCxsAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="07" title="07" src="/static/79c37854f26c19eaa14f7d3ddb294216/a331c/2022-10-28-07.png" srcset="/static/79c37854f26c19eaa14f7d3ddb294216/36ca5/2022-10-28-07.png 200w, /static/79c37854f26c19eaa14f7d3ddb294216/a3397/2022-10-28-07.png 400w, /static/79c37854f26c19eaa14f7d3ddb294216/a331c/2022-10-28-07.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>If the 3rd transaction was the fastest, we try to query by the last processed position before the 1st and 2nd were committed, then we won’t know if the gap was caused by some transaction rollbacks or race condition. <strong>If we optimistically assume that the gaps were caused by rollbacks, we may lose messages.</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/18234f66fdd47135465a32320f78e26e/a331c/2022-10-28-08.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACQUlEQVQozx2RS2sTUQBG55+5dOPWhf4Ed4KggojgwkVBaheVStUWtT66MBTBIq2ptprGJmmeYyaZybzv+859zJ1JxrqQuPs4HM7mszJZYK4glZBKzCSXBZc5YWJJmEJMZ6qU+SLTJaASEomWmqJZToWxMNNMliQziClAJGKSqAXWfyHTmOeQLrtMmCglEFGI+dgNU0QwYQmkVhDj3mjipyAmJMEkYbnTqPV2H6QQRAiTLMc8R0x7ARBKgzg8+/YZIyCV9iNghSkZ2m5CaIRQANKQ8OHp3lltJWbCJxzQjGS50FUY4y9Hzf13G83Vq50fh92Ba489S5oqRbxvTyKMEReYcqwW1FwM39+d1rcTrrgshsH4e+f4w8He7ptHrY3r+wfbtZNPneHAYrKIIXW8EHKechFy6QMQpnH78PX4vA64oFkeYjwYDT4+f1ZbuX1y78rXl6v7O1vdVtsCRFJRuH7c7Dn9+tvG2rXAGydUhVRHVEEiAZFMLWYTv7H+4uj+rcaNS+drj7ubr+xf5xbJDM1MDKif0DSazDoHAEMsFKQMc8llyWTJ9Hw2Derrm4cP75zevNx6+qS9tWO3uhaky4cwzxPEuyN3HOBZhCazxPEix4udWewGKaAqRZk3Cab2ZDoYRm4YelEKmEWFYcJkuowhPf7ZPGv3+vZ08HvaGzn9keO4gesniCmuykwvhKm4qTwkY8hSLCypS5mXQhfazMvqwswrXSzysjLln7yo8v9bmbnKS6kKoYpMmQBxiBlh8h/JkTwSDVOaKgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="08" title="08" src="/static/18234f66fdd47135465a32320f78e26e/a331c/2022-10-28-08.png" srcset="/static/18234f66fdd47135465a32320f78e26e/36ca5/2022-10-28-08.png 200w, /static/18234f66fdd47135465a32320f78e26e/a3397/2022-10-28-08.png 400w, /static/18234f66fdd47135465a32320f78e26e/a331c/2022-10-28-08.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We’ll update the position with the newly read maximum one (13), and then the following query will use it, skipping records added after that (11 and 12).</p> <p><strong>We could workaround the ordering issues, but losing messages is a no-go for most systems.</strong></p> <p>What could we do?</p> <p>A first thing is a nuke option. It’s nicely presented by <a href="https://www.cybertec-postgresql.com/en/gaps-in-sequences-postgresql/">Laurenz Albe in his article</a>. <strong>We could manually generate gapless numbers in the “singleton table”.</strong> W could do it like that:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> seq <span class="token punctuation">(</span>id <span class="token keyword">bigint</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">INSERT</span> <span class="token keyword">INTO</span> seq <span class="token punctuation">(</span>id<span class="token punctuation">)</span> <span class="token keyword">VALUES</span> <span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">CREATE</span> <span class="token keyword">FUNCTION</span> next_val<span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">RETURNS</span> <span class="token keyword">bigint</span> <span class="token keyword">LANGUAGE</span> <span class="token keyword">sql</span> <span class="token keyword">AS</span> <span class="token string">'UPDATE seq SET id = id + 1 RETURNING id'</span><span class="token punctuation">;</span></code></pre></div> <p>It would update the number in the table only if the transaction succeeded. Why is it a nuke option? Because it’s locking the whole <em>seq</em> table making all the processing that’s calling this function sequential. This could be fine for low throughput but high importance numbers, but not for the mechanism like outbox that’ll be the backbone of the communication. Trust me, I tried that, and the performance hit was the backbone of the communication was too big to accept.</p> <p><strong>The next option is to do gap detection like Marten is doing.</strong> If we delay the message publishing by waiting a bit, we could try to assume that the gaps have come from rolled-back transactions. If we found such, we could append <em>“tombstone event”,</em> marking the gap as checked and locked. Once we fill all the holes with such events, we can continue processing. In Marten, it’s called <em>High Water Mark</em>. It’s the furthest known event sequence that the daemon “knows” that all events with that sequence or lower can be safely processed in order by projections. The high watermark will frequently be a little behind the highest known event sequence number if outstanding gaps in the event sequence are detected.</p> <p>Yet, writing such a gap detection mechanism is non-trivial. <a href="https://www.youtube.com/watch?v=rm2lFlI3Ubk">Jeremy explained some of the challenges in his talk at Event Sourcing Live</a>.</p> <p><strong>What if we could just live with those gaps?</strong> I said we should not, so am I doing some Jedi mind tricks? And that’s how we come up with the last idea. There’s <em>a trick…</em></p> <p>Postgres has a mechanism of returning us information about current active transactions. We can get such information by calling <em>pg_current_snapshot</em> function. Once we have it, we can call on the result of another function, <em>pg_snapshot_xmin</em>, which selects the minimum transaction id.</p> <p><strong>So if call <em>pg_snapshot_xmin(pg_current_snapshot())</em>, we’ll effectively get the minimum active transaction id.</strong> In Postgres, transactions are monotonic, gapless numbers that are unique throughout the cluster lifetime. We could use that for our needs. We could add a transaction id column to our outbox table and fill it with the transaction id to which we’re appending the message.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">ALTER</span> <span class="token keyword">TABLE</span> outbox <span class="token keyword">ADD</span> <span class="token keyword">COLUMN</span> transaction_id xid8 <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">;</span></code></pre></div> <p>We’re using <em>xid8</em> type, a 64bit, sequential number type used to represent transaction ids. Having that, we could change our query to use it:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> position<span class="token punctuation">,</span> message_id<span class="token punctuation">,</span> message_type<span class="token punctuation">,</span> <span class="token keyword">data</span> <span class="token keyword">FROM</span> outbox <span class="token keyword">WHERE</span> position <span class="token operator">></span> last_processed_position <span class="token operator">AND</span> transaction_id <span class="token operator">&lt;</span> pg_snapshot_xmin<span class="token punctuation">(</span>pg_current_snapshot<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">ORDER</span> <span class="token keyword">BY</span> position <span class="token keyword">ASC</span> <span class="token keyword">LIMIT</span> <span class="token number">100</span><span class="token punctuation">;</span></code></pre></div> <p>Thanks to that, we won’t get the “Usain Bolts”, so the messages from the transactions were faster than transactions that started earlier.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e6349b2df1f068e14d9516b4163f445f/a331c/2022-10-28-10.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAsTAAALEwEAmpwYAAACKElEQVQozyWQS08TURzF51OamPgF3Lh169YYNywwLgwGYxUImIZX5C2gJYAthbZMy0zncd/3/ufOvXceLQkL05qc1Tm/cxbH07YSmWXKMGUEWG0qbQoJ+cwBy8FpW5liql1NlaHS8DmmtFN56QlwYGqpCw5uloGRdircEwMrsoIpy6SBvEyJolxRAX6QUK6EBEyVFyNx2x9FhCIpkZAIimGrebP2hjCWcCF0IbKZwoTmxlEUX//apTjNjY1S6iVE9gYBEoA4wxRhCX6r2f7xloJGUnEJmZlc3l8fXZ0cnf/d/f6xs/hir7mys3/qD8eeKR8J13eDEWcUIFNKga3zYuqvvB4fL0nIQTvtJgnmvSG62t94WH7ZPj/s3D2EMfbAVIipURgDSAEZA40pJiS5PVgO24cKQM3L4wRvHGytNN5tLzxbXVv8tPq5ez/wqDQqr4IxuuiOuseN1sLzOPSJsqlyaP7Wf8APotW99S/f3jeXXjXWP3zdanT6PU/oUukSURVhRdJR2N7HnHJtqJQiy8FUYOrMTsYpG/hRpztMmbnpjfxh9BBhj87nReYwzzr9IMBZyiBCPExYENMwYWHCqLRE6DChQUzilCaIJ4hToT2Vl5CX2tWIqt8XV5vbuzs/D07OWntHp2d/LluX7b4fSl1kpspmR07A1iHTiCrCtWdcbYo6d5Ut6snjEyI8jNA4xuMYE6YQEdqUrpzOMFcbW2lXRSxjAiSYfxK/P+0YTHZCAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="10" title="10" src="/static/e6349b2df1f068e14d9516b4163f445f/a331c/2022-10-28-10.png" srcset="/static/e6349b2df1f068e14d9516b4163f445f/36ca5/2022-10-28-10.png 200w, /static/e6349b2df1f068e14d9516b4163f445f/a3397/2022-10-28-10.png 400w, /static/e6349b2df1f068e14d9516b4163f445f/a331c/2022-10-28-10.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>If we have a scenario like in the image above, as we have transaction 129 ongoing, then through our query, we’ll get record 11, as it was added in transaction no 128. We won’t get the record with position 13, as it was added by the transaction that started later than the ongoing one.</p> <p>It looks good enough, but let’s discuss one more scenario. Let’s say that we have the same set of transactions again. Yet, this time 128, so the earliest one finished first. Still, it got the global position later than the still active 129.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e978495db06b90eca11a1647028c8e6f/a331c/2022-10-28-11.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACB0lEQVQoz0VS2WoUURDtj9QP8MUf8Qvy4JvGp4AgIWAUEbJgSCa7TnomPUtPd890912q6q69TRIRpCeIcDjUOVS9nFOB8a1UXpCX5EB57VrtWtQVl1qglRvHNQ/GdxydIA/KS+VRV2SaAHSt/RpMxckwNIKsVKYELavf6B8l9QfKtqsCBJAANZ4mpUAkVXIK0lwOR9MlFyVpRroAlUsshBjtvolOdrB6AlWBrhdZqbQtlourw71VlihtspwFOadRFJeomJQFL3NWlqiENrf7W+OzPTA1msrWj6sCjk5vDnY/3L97dfzl0/fD01mcBb59yhmG46mUnLQm3yVH2+H716Z+0NUjSG5cG6bRILwIp/ng687o7cuLw88n5z/jNA+0awtO6bJQipAItUuHx7MfH4FliAJBKtPkgFEcD8fzy7OT2WD/8vz88jacLVaBQEe2ncyz6zCeHmxfb73gZUHVE2MlFyBUxdFpv15kLLyfX/2KUu5vhpNRFM+SPEDToGkKTitGbHGXXX8DliMCggRlwTTY19ElKz6ZL4dhVDC4G08ns3SRlYEgL9DJPtIK7FrVf0DXz80L8v+6rZk0WS6SlViWkG/AwQRkG2Ub5Xom04ByqGtyrdp8i7It2Zo2C9p3tk9xnQibM2LSBK7qXNXaqnU9Old3tpf/2dWdb9b+efa9mUnNJQHZv7sFSzivDilJAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="11" title="11" src="/static/e978495db06b90eca11a1647028c8e6f/a331c/2022-10-28-11.png" srcset="/static/e978495db06b90eca11a1647028c8e6f/36ca5/2022-10-28-11.png 200w, /static/e978495db06b90eca11a1647028c8e6f/a3397/2022-10-28-11.png 400w, /static/e978495db06b90eca11a1647028c8e6f/a331c/2022-10-28-11.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>If we query now using the sequence number to filter out already processed records, we’ll lose the message from transaction 129. It has a smaller number (11) than the one processed in transaction 128 (12).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e978495db06b90eca11a1647028c8e6f/a331c/2022-10-28-11.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACB0lEQVQoz0VS2WoUURDtj9QP8MUf8Qvy4JvGp4AgIWAUEbJgSCa7TnomPUtPd890912q6q69TRIRpCeIcDjUOVS9nFOB8a1UXpCX5EB57VrtWtQVl1qglRvHNQ/GdxydIA/KS+VRV2SaAHSt/RpMxckwNIKsVKYELavf6B8l9QfKtqsCBJAANZ4mpUAkVXIK0lwOR9MlFyVpRroAlUsshBjtvolOdrB6AlWBrhdZqbQtlourw71VlihtspwFOadRFJeomJQFL3NWlqiENrf7W+OzPTA1msrWj6sCjk5vDnY/3L97dfzl0/fD01mcBb59yhmG46mUnLQm3yVH2+H716Z+0NUjSG5cG6bRILwIp/ng687o7cuLw88n5z/jNA+0awtO6bJQipAItUuHx7MfH4FliAJBKtPkgFEcD8fzy7OT2WD/8vz88jacLVaBQEe2ncyz6zCeHmxfb73gZUHVE2MlFyBUxdFpv15kLLyfX/2KUu5vhpNRFM+SPEDToGkKTitGbHGXXX8DliMCggRlwTTY19ElKz6ZL4dhVDC4G08ns3SRlYEgL9DJPtIK7FrVf0DXz80L8v+6rZk0WS6SlViWkG/AwQRkG2Ub5Xom04ByqGtyrdp8i7It2Zo2C9p3tk9xnQibM2LSBK7qXNXaqnU9Old3tpf/2dWdb9b+efa9mUnNJQHZv7sFSzivDilJAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="12" title="12" src="/static/e978495db06b90eca11a1647028c8e6f/a331c/2022-10-28-11.png" srcset="/static/e978495db06b90eca11a1647028c8e6f/36ca5/2022-10-28-11.png 200w, /static/e978495db06b90eca11a1647028c8e6f/a3397/2022-10-28-11.png 400w, /static/e978495db06b90eca11a1647028c8e6f/a331c/2022-10-28-11.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>To solve that, the only option we have is to filter by the transaction id instead of the global position. We’re benefiting from the fact that a transaction id is a monotonic number.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/836fb7b60d6425bc9c96ecc8db573139/a331c/2022-10-28-13.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACMUlEQVQozx2S204TQQCG9ylNTPQBeBdviIR44YUCwQMNKgKCUkKJgNBiS227bXe3uzOzcz7szJ5aCCam/hffzfdd/p5xFVOOKUel5dJqW2lbCZ0TrqnImMq1rWyxMK6m0hFhmbRcOWkKlZUe14WytTDFf5cx5bheRtw9SvfApKXCqqwEqWBCc2nGIcBMCqVTKr0E8bvBJMY0FRJzlXIJKYeUDr++GJ1tCrfgKmc6DxOcuZwz3GkdpGBmXZFA4gEs+qMACwUZTygBBAOp06xof13tnr2jpuA6z/IHhOXJ+fXx7sbv9afNzxv731sjP/RseY+o6g8nkAmmNMsX0cWH/psV7mpRPKaca1v5ILi8+zUO6XVzb/DqWaf55aozDCPoaVshIqcxpMpQpYmtxr1W7+QtJhALmQohTQkY7Qfj/dbRTmPtYP15o7G2tff+dtD3iLDSlMEMte+m47Ptm9UnaRKJ/BFjgikj0mGeaTuPAG6cfNr6uLr7emV75+XG3ma73/WEKYUpEZEx4izx4/Y3hqFcTgmVyaySpjL5IkZsEsBOzw8S3v0zHfrRNIQelY4Ky3WBuen7s5hazDOARYxYjJacQQqwnEQwTHCY4BhigBhIOWHak7ZUtsyKOSLy59XN/uHR4XHz9Pzyx2nr6qZ7en5x2xtEgIymM5mVy7fki5hbRBRhxsvyuXFz7Wqdz4vF3whSPwRBnE5jBLCYQSK0y+sHV95rVytbKVcHVEOqMDf/ALUwPr0NUt65AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="13" title="13" src="/static/836fb7b60d6425bc9c96ecc8db573139/a331c/2022-10-28-13.png" srcset="/static/836fb7b60d6425bc9c96ecc8db573139/36ca5/2022-10-28-13.png 200w, /static/836fb7b60d6425bc9c96ecc8db573139/a3397/2022-10-28-13.png 400w, /static/836fb7b60d6425bc9c96ecc8db573139/a331c/2022-10-28-13.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>This filter will give us a guarantee of not losing any messages. It is a much better option than the previous nuke option, as we don’t have a performance hit while adding new messages (besides using more data). Our queries may be a bit delayed if our transactions last long, so we should ensure they’re processed quickly. Worth noting is that our ordering guarantee respects when the transaction was started, not when the message was appended. That should be fine for most cases, but in some edge cases, it may create problems if forgotten.</p> <p>The last thing to mention is that one transaction can append more than one message. In that case, we would only like to be polling the same message once (e.g. in case of retry). The final SQL query to respect this would be:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> position<span class="token punctuation">,</span> message_id<span class="token punctuation">,</span> message_type<span class="token punctuation">,</span> <span class="token keyword">data</span> <span class="token keyword">FROM</span> outbox <span class="token keyword">WHERE</span> <span class="token punctuation">(</span> <span class="token punctuation">(</span>transaction_id <span class="token operator">=</span> last_processed_transaction_id <span class="token operator">AND</span> position <span class="token operator">></span> last_processed_position<span class="token punctuation">)</span> <span class="token operator">OR</span> <span class="token punctuation">(</span>transaction_id <span class="token operator">></span> last_processed_transaction_id<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token operator">AND</span> transaction_id <span class="token operator">&lt;</span> pg_snapshot_xmin<span class="token punctuation">(</span>pg_current_snapshot<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">ORDER</span> <span class="token keyword">BY</span> transaction_id <span class="token keyword">ASC</span><span class="token punctuation">,</span> position <span class="token keyword">ASC</span> <span class="token keyword">LIMIT</span> <span class="token number">100</span><span class="token punctuation">;</span></code></pre></div> <p>To sum up, as always, the devil lies in details. Good and useful patterns are not always easy to implement. We should always ensure that we defined our expected guarantees upfront and that they’re matched. But hey, isn’t that what we’re getting paid for?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Big thanks to <a href="https://twitter.com/Masternak">Tomek Masternak</a> for reviewing the article and pointing out the last use case I didn’t initially predict.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Writing and testing business logic in F#]]>https://event-driven.io/en/writing_and_testing_business_logic_in_fsharp/https://event-driven.io/en/writing_and_testing_business_logic_in_fsharp/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/25d90fad4e81b76cff5288e985d5b082/8299d/2022-10-23-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAeFUaSD/xAAZEAEAAgMAAAAAAAAAAAAAAAABEBECITH/2gAIAQEAAQUCC3MqDruP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQAGPwJf/8QAGhAAAgIDAAAAAAAAAAAAAAAAAREAECFR0f/aAAgBAQABPyFSNzCPKJAYbOv/2gAMAwEAAgADAAAAEEAP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAHRABAAICAgMAAAAAAAAAAAAAAREhABAxsUFxkf/aAAgBAQABPxC6foO8dJBNU9DVEDDw4sivXjX/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/25d90fad4e81b76cff5288e985d5b082/c60e9/2022-10-23-cover.jpg" srcset="/static/25d90fad4e81b76cff5288e985d5b082/37402/2022-10-23-cover.jpg 200w, /static/25d90fad4e81b76cff5288e985d5b082/4cda9/2022-10-23-cover.jpg 400w, /static/25d90fad4e81b76cff5288e985d5b082/c60e9/2022-10-23-cover.jpg 800w, /static/25d90fad4e81b76cff5288e985d5b082/6c738/2022-10-23-cover.jpg 1200w, /static/25d90fad4e81b76cff5288e985d5b082/56dca/2022-10-23-cover.jpg 1600w, /static/25d90fad4e81b76cff5288e985d5b082/8299d/2022-10-23-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>My road to functional programming was pretty long.</strong> I started with structural programming in C++ and then rebranded into an Object-Oriented approach with Java and C#. At some point, I began to feel that <a href="/en/onion_clean_code/">the ceremony I had been doing was slowing me down</a>. I started to code in JavaScript more. First, I was afraid; I was petrified. Like most of the backend devs, I was a JS hater. Then, I understood that it’s just looking similar to OOP languages, and it’s much more efficient if it’s done without the layered ceremonies, using just functions and objects. To this day, I think that adding classes in Ecma Script 6 was a mistake. Yet, I haven’t ever written any production code with pure functional language. The closer to that was in <a href="/en/type_script_node_Js_event_sourcing/">TypeScript, which can be used in this way</a>.</p> <p><strong>As most of my career is related to coding in .NET, I have always wanted to try F#.</strong> I like its concise syntax and precision in defining the type-driven approach. Especially reading <a href="https://pragprog.com/titles/swdddf/domain-modeling-made-functional/">“Domain Modeling Made Functional” by Scott Wlaschin</a> made me firm about the benefits of using it for business logic. Even if you don’t want to code in F#, I highly recommend this book, it was eyes opening experience for me, and it’s one of the best programming books I’ve read in the last few years. Also, numerous discussions with my friend <a href="https://twitter.com/rbartelink">Ruben Bartelink</a> and <a href="https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider">influence by Decider pattern by Jérémie Chassaing</a> increased my will to try the language. Still, I didn’t have enough time to pass the ramp-up phase.</p> <p><strong>Two weeks ago, I wrote about <a href="/en/testing_event_sourcing/">testing business logic in Event Sourcing</a> and how my <a href="https://github.com/oskardudycz/Ogooreck">testing library called Ogooreck</a> can help you in that.</strong> I wrote it with a functional approach in mind, as <a href="/en/union_types_in_csharp/">C# allows you to put a bit of that flavour, but not fully</a>. I thought that this could be a decent chance to try F#. I decided to port my samples and see how Ogooreck’s API goes with it. Let’s see what I came up with!</p> <p><strong>Let’s start with the entity definition. Even though I claimed that <a href="/en/bank_account_event_sourcing/">Bank Account is not the best example for explaining Event Sourcing</a>, I used it, as for baby steps, it is actually okay.</strong></p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">type</span> <span class="token class-name">BankAccount</span> <span class="token operator">=</span> <span class="token operator">|</span> Initial <span class="token operator">|</span> Open <span class="token keyword">of</span> <span class="token punctuation">{</span><span class="token operator">|</span> Id<span class="token punctuation">:</span> <span class="token class-name">AccountId</span> Balance<span class="token punctuation">:</span> <span class="token class-name">decimal</span> Version<span class="token punctuation">:</span> <span class="token class-name">int64</span> <span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> Closed <span class="token keyword">of</span> <span class="token punctuation">{</span><span class="token operator">|</span> Id<span class="token punctuation">:</span> <span class="token class-name">AccountId</span><span class="token punctuation">;</span> Version<span class="token punctuation">:</span> <span class="token class-name">int64</span> <span class="token operator">|</span><span class="token punctuation">}</span></code></pre></div> <p>This states that Bank Account can either have an initial/empty state or open or closed. F# supports union types and allows different definitions for different entity states. Think of it as a state machine; we’re transitioning from one distinct state to another.</p> <p>Now, let’s define events:</p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">type</span> <span class="token class-name">BankAccountOpened</span> <span class="token operator">=</span> <span class="token punctuation">{</span> BankAccountId<span class="token punctuation">:</span> <span class="token class-name">AccountId</span> AccountNumber<span class="token punctuation">:</span> <span class="token class-name">AccountNumber</span> ClientId<span class="token punctuation">:</span> <span class="token class-name">ClientId</span> CurrencyIsoCode<span class="token punctuation">:</span> <span class="token class-name">CurrencyIsoCode</span> CreatedAt<span class="token punctuation">:</span> <span class="token class-name">DateTimeOffset</span> Version<span class="token punctuation">:</span> <span class="token class-name">int64</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">DepositRecorded</span> <span class="token operator">=</span> <span class="token punctuation">{</span> BankAccountId<span class="token punctuation">:</span> <span class="token class-name">AccountId</span> Amount<span class="token punctuation">:</span> <span class="token class-name">decimal</span> CashierId<span class="token punctuation">:</span> <span class="token class-name">CashierId</span> RecordedAt<span class="token punctuation">:</span> <span class="token class-name">DateTimeOffset</span> Version<span class="token punctuation">:</span> <span class="token class-name">int64</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">CashWithdrawnFromATM</span> <span class="token operator">=</span> <span class="token punctuation">{</span> BankAccountId<span class="token punctuation">:</span> <span class="token class-name">AccountId</span> Amount<span class="token punctuation">:</span> <span class="token class-name">decimal</span> AtmId<span class="token punctuation">:</span> <span class="token class-name">AtmId</span> RecordedAt<span class="token punctuation">:</span> <span class="token class-name">DateTimeOffset</span> Version<span class="token punctuation">:</span> <span class="token class-name">int64</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">BankAccountClosed</span> <span class="token operator">=</span> <span class="token punctuation">{</span> BankAccountId<span class="token punctuation">:</span> <span class="token class-name">AccountId</span> Reason<span class="token punctuation">:</span> <span class="token class-name">string</span> ClosedAt<span class="token punctuation">:</span> <span class="token class-name">DateTimeOffset</span> Version<span class="token punctuation">:</span> <span class="token class-name">int64</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token operator">|</span> BankAccountOpened <span class="token keyword">of</span> <span class="token class-name">BankAccountOpened</span> <span class="token operator">|</span> DepositRecorded <span class="token keyword">of</span> <span class="token class-name">DepositRecorded</span> <span class="token operator">|</span> CashWithdrawnFromAtm <span class="token keyword">of</span> <span class="token class-name">CashWithdrawnFromATM</span> <span class="token operator">|</span> BankAccountClosed <span class="token keyword">of</span> <span class="token class-name">BankAccountClosed</span></code></pre></div> <p>Those are all facts that can be registered during the Bank Account lifetime (I told you already that’s oversimplified, right?). I also grouped all the events into <em>Event</em> union type. Why? Have a look at this method:</p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">let</span> evolve bankAccount bankAccountEvent <span class="token punctuation">:</span> <span class="token class-name">BankAccount</span> <span class="token operator">=</span> <span class="token keyword">match</span> bankAccount<span class="token punctuation">,</span> bankAccountEvent <span class="token keyword">with</span> <span class="token operator">|</span> Initial _<span class="token punctuation">,</span> BankAccountOpened <span class="token keyword">event</span> <span class="token operator">-></span> <span class="token computation-expression keyword">Open</span> <span class="token punctuation">{</span><span class="token operator">|</span> Id <span class="token operator">=</span> <span class="token keyword">event</span><span class="token punctuation">.</span>BankAccountId Balance <span class="token operator">=</span> <span class="token number">0M</span> Version <span class="token operator">=</span> <span class="token number">1L</span> <span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> Open state<span class="token punctuation">,</span> DepositRecorded <span class="token keyword">event</span> <span class="token operator">-></span> <span class="token computation-expression keyword">Open</span> <span class="token punctuation">{</span><span class="token operator">|</span> state <span class="token keyword">with</span> Balance <span class="token operator">=</span> state<span class="token punctuation">.</span>Balance <span class="token operator">+</span> <span class="token keyword">event</span><span class="token punctuation">.</span>Amount Version <span class="token operator">=</span> <span class="token keyword">event</span><span class="token punctuation">.</span>Version <span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> Open state<span class="token punctuation">,</span> CashWithdrawnFromAtm <span class="token keyword">event</span> <span class="token operator">-></span> <span class="token computation-expression keyword">Open</span> <span class="token punctuation">{</span><span class="token operator">|</span> state <span class="token keyword">with</span> Balance <span class="token operator">=</span> state<span class="token punctuation">.</span>Balance <span class="token operator">-</span> <span class="token keyword">event</span><span class="token punctuation">.</span>Amount Version <span class="token operator">=</span> <span class="token keyword">event</span><span class="token punctuation">.</span>Version <span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> Open state<span class="token punctuation">,</span> BankAccountClosed bankAccountClosed <span class="token operator">-></span> <span class="token computation-expression keyword">Closed</span> <span class="token punctuation">{</span><span class="token operator">|</span> Id <span class="token operator">=</span> state<span class="token punctuation">.</span>Id Version <span class="token operator">=</span> bankAccountClosed<span class="token punctuation">.</span>Version <span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> _ <span class="token operator">-></span> bankAccount</code></pre></div> <p><strong>The method defines the state transition based on the facts observed during the bank account lifetime.</strong> In Event Sourcing, to <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">build the current state</a>, we’re getting all the events registered for the specific entity, and one by one applying it on the state. We do that by calling such a function as the one shown above.</p> <p>The syntax, at first glance, may look a bit cryptic. But if we look closer, then it’s actually straightforward. We’re doing pattern matching based on the current bank account state and the event.</p> <p>As we expect, certain operations happen in particular states, for instance:</p> <ul> <li>opening should be only available if no such account exists,</li> <li>recording transactions and closure should be done on an open account,</li> <li>we should not be opening or closing the bank accounts twice,</li> </ul> <p>then we’re reflecting each of those cases in code. That’s also the power of F#; it allows us to make explicit what should be made explicit. If we encounter an unexpected state, it either is incorrect or was correct in the past, and our code doesn’t reflect reality. As I wrote in <a href="/en/should_you_throw_exception_when_rebuilding_state_from_events/">the other article, we should not throw exceptions here</a>. Our business logic should guard us later on.</p> <h2 id="business-logic" style="position:relative;"><a href="#business-logic" aria-label="business logic permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Business Logic</h2> <p><strong>Speaking about business logic, let’s define it.</strong> Our bank account can handle the following user intentions:</p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">type</span> <span class="token class-name">OpenBankAccount</span> <span class="token operator">=</span> <span class="token punctuation">{</span> BankAccountId<span class="token punctuation">:</span> <span class="token class-name">AccountId</span> AccountNumber<span class="token punctuation">:</span> <span class="token class-name">AccountNumber</span> ClientId<span class="token punctuation">:</span> <span class="token class-name">ClientId</span> CurrencyIsoCode<span class="token punctuation">:</span> <span class="token class-name">CurrencyIsoCode</span> Now<span class="token punctuation">:</span> <span class="token class-name">DateTimeOffset</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">RecordDeposit</span> <span class="token operator">=</span> <span class="token punctuation">{</span> Amount<span class="token punctuation">:</span> <span class="token class-name">decimal</span><span class="token punctuation">;</span> CashierId<span class="token punctuation">:</span> <span class="token class-name">CashierId</span><span class="token punctuation">;</span> Now<span class="token punctuation">:</span> <span class="token class-name">DateTimeOffset</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">WithdrawCashFromAtm</span> <span class="token operator">=</span> <span class="token punctuation">{</span> Amount<span class="token punctuation">:</span> <span class="token class-name">decimal</span><span class="token punctuation">;</span> AtmId<span class="token punctuation">:</span> <span class="token class-name">AtmId</span><span class="token punctuation">;</span> Now<span class="token punctuation">:</span> <span class="token class-name">DateTimeOffset</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">CloseBankAccount</span> <span class="token operator">=</span> <span class="token punctuation">{</span> Reason<span class="token punctuation">:</span> <span class="token class-name">string</span><span class="token punctuation">;</span> Now<span class="token punctuation">:</span> <span class="token class-name">DateTimeOffset</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">Command</span> <span class="token operator">=</span> <span class="token operator">|</span> OpenBankAccount <span class="token keyword">of</span> <span class="token class-name">OpenBankAccount</span> <span class="token operator">|</span> RecordDeposit <span class="token keyword">of</span> <span class="token class-name">RecordDeposit</span> <span class="token operator">|</span> WithdrawCashFromAtm <span class="token keyword">of</span> <span class="token class-name">WithdrawCashFromAtm</span> <span class="token operator">|</span> CloseBankAccount <span class="token keyword">of</span> <span class="token class-name">CloseBankAccount</span></code></pre></div> <p>We could group the command handling, again using pattern matching:</p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">let</span> decide command bankAccount <span class="token operator">=</span> <span class="token keyword">match</span> command <span class="token keyword">with</span> <span class="token operator">|</span> OpenBankAccount c <span class="token operator">-></span> openBankAccount c bankAccount <span class="token operator">|</span> RecordDeposit c <span class="token operator">-></span> recordDeposit c bankAccount <span class="token operator">|</span> WithdrawCashFromAtm c <span class="token operator">-></span> withdrawCashFromAtm c bankAccount <span class="token operator">|</span> CloseBankAccount c <span class="token operator">-></span> closeBankAccount c bankAccount</code></pre></div> <p><strong>Each method is just a pure function that takes command, current state and returns event(s).</strong> See more:</p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">let</span> openBankAccount <span class="token punctuation">(</span>command<span class="token punctuation">:</span> <span class="token class-name">OpenBankAccount</span><span class="token punctuation">)</span> bankAccount <span class="token punctuation">:</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token keyword">match</span> bankAccount <span class="token keyword">with</span> <span class="token operator">|</span> Open _ <span class="token operator">-></span> invalidOp <span class="token string">"Account is already opened!"</span> <span class="token operator">|</span> Closed _ <span class="token operator">-></span> invalidOp <span class="token string">"Account is already closed!"</span> <span class="token operator">|</span> Initial _ <span class="token operator">-></span> <span class="token computation-expression keyword">BankAccountOpened</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> command<span class="token punctuation">.</span>BankAccountId AccountNumber <span class="token operator">=</span> command<span class="token punctuation">.</span>AccountNumber ClientId <span class="token operator">=</span> command<span class="token punctuation">.</span>ClientId CurrencyIsoCode <span class="token operator">=</span> command<span class="token punctuation">.</span>CurrencyIsoCode CreatedAt <span class="token operator">=</span> command<span class="token punctuation">.</span>Now Version <span class="token operator">=</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> recordDeposit <span class="token punctuation">(</span>command<span class="token punctuation">:</span> <span class="token class-name">RecordDeposit</span><span class="token punctuation">)</span> bankAccount <span class="token punctuation">:</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token keyword">match</span> bankAccount <span class="token keyword">with</span> <span class="token operator">|</span> Initial _ <span class="token operator">-></span> invalidOp <span class="token string">"Account is not opened!"</span> <span class="token operator">|</span> Closed _ <span class="token operator">-></span> invalidOp <span class="token string">"Account is closed!"</span> <span class="token operator">|</span> Open state <span class="token operator">-></span> <span class="token computation-expression keyword">DepositRecorded</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> state<span class="token punctuation">.</span>Id Amount <span class="token operator">=</span> command<span class="token punctuation">.</span>Amount CashierId <span class="token operator">=</span> command<span class="token punctuation">.</span>CashierId RecordedAt <span class="token operator">=</span> command<span class="token punctuation">.</span>Now Version <span class="token operator">=</span> state<span class="token punctuation">.</span>Version <span class="token operator">+</span> <span class="token number">1L</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> withdrawCashFromAtm <span class="token punctuation">(</span>command<span class="token punctuation">:</span> <span class="token class-name">WithdrawCashFromAtm</span><span class="token punctuation">)</span> bankAccount <span class="token punctuation">:</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token keyword">match</span> bankAccount <span class="token keyword">with</span> <span class="token operator">|</span> Initial _ <span class="token operator">-></span> invalidOp <span class="token string">"Account is not opened!"</span> <span class="token operator">|</span> Closed _ <span class="token operator">-></span> invalidOp <span class="token string">"Account is closed!"</span> <span class="token operator">|</span> Open state <span class="token operator">-></span> <span class="token keyword">if</span> <span class="token punctuation">(</span>state<span class="token punctuation">.</span>Balance <span class="token operator">&lt;</span> command<span class="token punctuation">.</span>Amount<span class="token punctuation">)</span> <span class="token keyword">then</span> invalidOp <span class="token string">"Not enough money!"</span> <span class="token computation-expression keyword">CashWithdrawnFromAtm</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> state<span class="token punctuation">.</span>Id Amount <span class="token operator">=</span> command<span class="token punctuation">.</span>Amount AtmId <span class="token operator">=</span> command<span class="token punctuation">.</span>AtmId RecordedAt <span class="token operator">=</span> command<span class="token punctuation">.</span>Now Version <span class="token operator">=</span> state<span class="token punctuation">.</span>Version <span class="token operator">+</span> <span class="token number">1L</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> closeBankAccount <span class="token punctuation">(</span>command<span class="token punctuation">:</span> <span class="token class-name">CloseBankAccount</span><span class="token punctuation">)</span> bankAccount <span class="token punctuation">:</span> <span class="token class-name">Event</span> <span class="token operator">=</span> <span class="token keyword">match</span> bankAccount <span class="token keyword">with</span> <span class="token operator">|</span> Initial _ <span class="token operator">-></span> invalidOp <span class="token string">"Account is not opened!"</span> <span class="token operator">|</span> Closed _ <span class="token operator">-></span> invalidOp <span class="token string">"Account is already closed!"</span> <span class="token operator">|</span> Open state <span class="token operator">-></span> <span class="token computation-expression keyword">BankAccountClosed</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> state<span class="token punctuation">.</span>Id Reason <span class="token operator">=</span> command<span class="token punctuation">.</span>Reason ClosedAt <span class="token operator">=</span> command<span class="token punctuation">.</span>Now Version <span class="token operator">=</span> state<span class="token punctuation">.</span>Version <span class="token operator">+</span> <span class="token number">1</span></code></pre></div> <p>Here we also see why having union types is helpful. We can explicitly restrict the handling based on the current bank account state. “Can” is, in fact, wrong. We “have to”. F# compiler will ask us to handle each possible state, helping us not to forget about anything. We also don’t need to check conditions based on fields (e.g. status, etc.) as our state will only contain available data.</p> <h2 id="testing" style="position:relative;"><a href="#testing" aria-label="testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Testing</h2> <p><strong>Still, I promised you the testing example with <a href="https://github.com/oskardudycz/Ogooreck">Ogooreck</a>.</strong> It’s a testing library that allows you to cut the boilerplate of the setup and verification and brings you the Behaviour-Driven Development (BDD) structure. Don’t worry; it’s not doing any custom <a href="https://en.wikipedia.org/wiki/Domain-specific_language">Domain-specific language (DSL)</a>. I like BDD because of its focus on behaviour. Still, I haven’t seen DSL workin well anywhere for a long time. The intention is to cut the boilerplate instead of adding it.</p> <p>For Event Sourcing, the pattern looks like this:</p> <ul> <li><strong>GIVEN</strong> set of events recorded for the entity,</li> <li><strong>WHEN</strong> we run the command on the state built from events,</li> <li><strong>THEN</strong> we’re getting new event(s) as a result of business logic.</li> </ul> <p>The tests can look as follows:</p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">open</span> Ogooreck<span class="token punctuation">.</span>BusinessLogic <span class="token keyword">open</span> FsCheck<span class="token punctuation">.</span>Xunit <span class="token keyword">let</span> random <span class="token operator">=</span> <span class="token function">Random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">let</span> spec <span class="token operator">=</span> Specification<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>decide<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> Initial<span class="token punctuation">)</span> <span class="token keyword">let</span> BankAccountOpenedWith bankAccountId now version <span class="token operator">=</span> <span class="token keyword">let</span> accountNumber <span class="token operator">=</span> AccountNumber<span class="token punctuation">.</span>parse <span class="token punctuation">(</span>Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">let</span> clientId <span class="token operator">=</span> ClientId<span class="token punctuation">.</span><span class="token function">newId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">let</span> currencyISOCode <span class="token operator">=</span> CurrencyIsoCode<span class="token punctuation">.</span>parse <span class="token string">"USD"</span> <span class="token computation-expression keyword">BankAccountOpened</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> bankAccountId AccountNumber <span class="token operator">=</span> accountNumber ClientId <span class="token operator">=</span> clientId CurrencyIsoCode <span class="token operator">=</span> currencyISOCode CreatedAt <span class="token operator">=</span> now Version <span class="token operator">=</span> version <span class="token punctuation">}</span> <span class="token keyword">let</span> BankAccountClosedWith bankAccountId now version <span class="token operator">=</span> <span class="token computation-expression keyword">BankAccountClosed</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> bankAccountId Reason <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> ClosedAt <span class="token operator">=</span> now Version <span class="token operator">=</span> version <span class="token punctuation">}</span> <span class="token annotation"><span class="token punctuation">[&lt;</span><span class="token class-name">Property</span><span class="token punctuation">>]</span></span> <span class="token keyword">let</span> ``GIVEN non existing bank account WHEN <span class="token keyword">open</span> <span class="token keyword">with</span> valid params THEN bank account is opened`` bankAccountId accountNumber clientId currencyISOCode now <span class="token operator">=</span> <span class="token keyword">let</span> notExistingAccount <span class="token operator">=</span> Array<span class="token punctuation">.</span>empty spec <span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span>notExistingAccount<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span> <span class="token computation-expression keyword">OpenBankAccount</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> bankAccountId AccountNumber <span class="token operator">=</span> accountNumber ClientId <span class="token operator">=</span> clientId CurrencyIsoCode <span class="token operator">=</span> currencyISOCode Now <span class="token operator">=</span> now <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> <span class="token computation-expression keyword">BankAccountOpened</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> bankAccountId AccountNumber <span class="token operator">=</span> accountNumber ClientId <span class="token operator">=</span> clientId CurrencyIsoCode <span class="token operator">=</span> currencyISOCode CreatedAt <span class="token operator">=</span> now Version <span class="token operator">=</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token operator">|></span> ignore <span class="token annotation"><span class="token punctuation">[&lt;</span><span class="token class-name">Property</span><span class="token punctuation">>]</span></span> <span class="token keyword">let</span> ``GIVEN <span class="token keyword">open</span> bank account WHEN record deposit <span class="token keyword">with</span> valid params THEN deposit is recorded`` bankAccountId amount cashierId now <span class="token operator">=</span> spec <span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span>BankAccountOpenedWith bankAccountId now <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span> <span class="token computation-expression keyword">RecordDeposit</span> <span class="token punctuation">{</span> Amount <span class="token operator">=</span> amount CashierId <span class="token operator">=</span> cashierId Now <span class="token operator">=</span> now <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> <span class="token computation-expression keyword">DepositRecorded</span> <span class="token punctuation">{</span> BankAccountId <span class="token operator">=</span> bankAccountId Amount <span class="token operator">=</span> amount CashierId <span class="token operator">=</span> cashierId RecordedAt <span class="token operator">=</span> now Version <span class="token operator">=</span> <span class="token number">2</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token operator">|></span> ignore <span class="token annotation"><span class="token punctuation">[&lt;</span><span class="token class-name">Property</span><span class="token punctuation">>]</span></span> <span class="token keyword">let</span> ``GIVEN closed bank account WHEN record deposit <span class="token keyword">with</span> valid params THEN fails <span class="token keyword">with</span> invalid operation <span class="token keyword">exception</span>`` bankAccountId amount cashierId now <span class="token operator">=</span> spec <span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span> BankAccountOpenedWith bankAccountId now <span class="token number">1</span><span class="token punctuation">,</span> BankAccountClosedWith bankAccountId now <span class="token number">2</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span> <span class="token computation-expression keyword">RecordDeposit</span> <span class="token punctuation">{</span> Amount <span class="token operator">=</span> amount CashierId <span class="token operator">=</span> cashierId Now <span class="token operator">=</span> now <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span>ThenThrows<span class="token operator">&lt;</span>InvalidOperationException<span class="token operator">></span> <span class="token operator">|></span> ignore</code></pre></div> <p>As you noticed, we’re setting up test specifications for the methods defined earlier.</p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">let</span> spec <span class="token operator">=</span> Specification<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>decide<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> Initial<span class="token punctuation">)</span></code></pre></div> <p>Then, we can just focus on defining the test scenario. Ogooreck will run internally the <em>evolve</em> function on provided events building the current state, then run business logic from the <em>decide</em> function for a specific command. It’ll also check the correctness of the result.</p> <p><strong>I think this gives structure to our tests and makes them read as documentation.</strong></p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p>So far, I like the F# experience. If we keep the functional shortening on the leash and focus on composition, we get concise, self-explanatory code. It’s also secure; if we do proper type definitions, we can reduce the chance of wrong cases and even testing scenarios.</p> <p>I’m still early in the journey, but <a href="https://github.com/oskardudycz/Ogooreck/pull/13">thanks to superb input from the community in my PR</a> learned a lot. I love the vibe in the F# community and the willingness to help such noobs like me.</p> <p>Some stuff in F# was confusing for me, like:</p> <ul> <li>the multiple ways of defining types, where to use when,</li> <li>different styles of function definitions (where to use parens and where not),</li> <li>the fact that formatting matters and the compiler is not extremely helpful if you don’t put the correct number of tabs.</li> </ul> <p>But, oh well, that’s a learning curve and minor things that can be found in each new learning.</p> <p>I liked the F# ecosystem so far, how the pieces match together and how much it already has baked in. I also like the tooling, e.g.:</p> <ul> <li><a href="https://fscheck.github.io/FsCheck/">FsCheck</a>: super simple test data seeding and <a href="https://en.wikipedia.org/wiki/Property_testing">Property Testing</a>.</li> <li><a href="https://github.com/fsprojects/FSharp.UMX">FSharp.UMX</a>: units of measure for primitive non-numeric types, like strongly-typed ids.</li> <li><a href="https://fsprojects.github.io/fantomas/">Fantomas</a> for elegant code autoformatting.</li> </ul> <p>The next step for me is to ensure that the F# experience is decent in <a href="https://martendb.io/">Marten</a>. But that’s for another story!</p> <p>Read also:</p> <ul> <li><a href="/en/testing_event_sourcing/">Testing business logic in Event Sourcing, and beyond!</a></li> <li><a href="/en/ogooreck_sneaky_bdd_testing_framework/">Ogooreck, a sneaky testing library in BDD style</a></li> <li><a href="/en/behaviour_driven_design_is_not_about_tests/">Behaviour-Driven Design is more than tests</a></li> <li><a href="/en/testing_event_driven_projections/">How to test event-driven projections</a></li> <li><a href="/en/i_tested_on_production/">I tested it on production and I’m not ashamed of it</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Push-based Outbox Pattern with Postgres Logical Replication]]>https://event-driven.io/en/push_based_outbox_pattern_with_postgres_logical_replication/https://event-driven.io/en/push_based_outbox_pattern_with_postgres_logical_replication/<p><strong>There are only a few patterns that I’m comfortable saying: <em>“You should use it always if you want to build mature system”</em>. One of them is the Outbox Pattern.</strong> Why? Because it guarantees that your business workflows and communication will not be stuck in the middle.</p> <p>As I explained in <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox, Inbox patterns and delivery guarantees explained</a>:</p> <p><em><strong>Outbox Pattern ensures that a message was sent (e.g. to a queue) successfully at least once.</strong> With this pattern, instead of directly publishing a message to the queue, we store it in temporary storage (e.g. database table). We’re wrapping the entity save and message storing with the Unit of Work (transaction). By that, we’re ensuring that the message won’t be lost if the application data is stored. It will be published later through a background process. This process will check if there are any unsent messages in the table. When the worker finds any, it tries to send them. After it gets confirmation of publishing (e.g. ACK from the queue), it marks the event as sent.</em></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/df0bc7f95463875eca32513aea9cf439/8299d/2022-10-13-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAABAAMBAAAAAAAAAAAAAAAAAAECAwX/xAAVAQEBAAAAAAAAAAAAAAAAAAAAAf/aAAwDAQACEAMQAAAB7cKmgT//xAAXEAEBAQEAAAAAAAAAAAAAAAABEBEx/9oACAEBAAEFAr0NZ//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABcQAQADAAAAAAAAAAAAAAAAABAAESH/2gAIAQEABj8Cbmn/xAAcEAABAwUAAAAAAAAAAAAAAAABABARITFRYYH/2gAIAQEAAT8hN1XLdCMkQjQb/9oADAMBAAIAAwAAABDD/wD/xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPxCH/8QAGhAAAwEAAwAAAAAAAAAAAAAAAAERITGBwf/aAAgBAQABPxBkkFitN6MHGooiy6US8Xg9If/Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/df0bc7f95463875eca32513aea9cf439/c60e9/2022-10-13-cover.jpg" srcset="/static/df0bc7f95463875eca32513aea9cf439/37402/2022-10-13-cover.jpg 200w, /static/df0bc7f95463875eca32513aea9cf439/4cda9/2022-10-13-cover.jpg 400w, /static/df0bc7f95463875eca32513aea9cf439/c60e9/2022-10-13-cover.jpg 800w, /static/df0bc7f95463875eca32513aea9cf439/6c738/2022-10-13-cover.jpg 1200w, /static/df0bc7f95463875eca32513aea9cf439/56dca/2022-10-13-cover.jpg 1600w, /static/df0bc7f95463875eca32513aea9cf439/8299d/2022-10-13-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Why does it provide at-least-once and not exactly-once? Writing to the database may fail (e.g. it will not respond). When that happens, the process handling outbox pattern will try to resend the event after some time and try to do it until the message is correctly marked as sent in the database._</p> <p>We can use Outbox Pattern to publish any type of message. Typically we’re sending commands or publishing events (see more in <a href="/en/whats_the_difference_between_event_and_command/">What’s the difference between a command and an event?</a> to know why semantics matters).</p> <p>If you’re using a relational database, you typically need to create the table for the messages.</p> <p>The example structure could look as follows:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> publications<span class="token punctuation">(</span> <span class="token comment">-- the autoincremented position of the message to respect the order</span> position BIGSERIAL <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token comment">-- this may allow you to partition publications, e.g. per tenant</span> publication_id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- unique message id, which can be used for deduplication or idempotency</span> message_id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- the message type, e.g. `TransactionRecorded`</span> message_type <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- serialised message data, e.g. to JSON</span> <span class="token keyword">data</span> JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- diagnostic information of when the message was scheduled</span> scheduled <span class="token keyword">TIMESTAMP</span> <span class="token keyword">WITH</span> <span class="token keyword">TIME</span> ZONE <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">default</span> <span class="token punctuation">(</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>If we’d like to have the option to have more than one subscriber and parallelise the publishing, then we should also define the table with information about the publishing process.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> subscribers <span class="token punctuation">(</span> <span class="token comment">-- subscription name</span> subscription_id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> <span class="token comment">-- information about the position of the last processed message</span> last_processed_position <span class="token keyword">INTEGER</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token comment">-- inform which publication we're subscribing to</span> publication_id <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We can subscribe to the specific message sequence (called <em>publication</em>) with such a table structure. Once we handle the particular message, using its position, we update the subscription’s last processed position. Thanks to that, we can retry it from the previously known position even if the process breaks in the middle.</p> <p><strong>Typically, this processing happens by polling.</strong> With a defined interval, we’re querying the messages table to get the new to publish. For instance:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> p<span class="token punctuation">.</span>position<span class="token punctuation">,</span> p<span class="token punctuation">.</span>publication_id<span class="token punctuation">,</span> p<span class="token punctuation">.</span>message_id<span class="token punctuation">,</span> p<span class="token punctuation">.</span>message_type<span class="token punctuation">,</span> p<span class="token punctuation">.</span><span class="token keyword">data</span><span class="token punctuation">,</span> p<span class="token punctuation">.</span>scheduled <span class="token keyword">FROM</span> publication p <span class="token keyword">WHERE</span> p<span class="token punctuation">.</span>publication_id <span class="token operator">=</span> subscription_publication_id p<span class="token punctuation">.</span>last_processed_position <span class="token operator">></span> subscription_last_processed_position <span class="token keyword">LIMIT</span> <span class="token number">100</span> </code></pre></div> <p>This is fine for many cases, but:</p> <ul> <li>it’s not easy to find the right interval. <a href="https://martendb.io/events/projections/async-daemon.html#async-projections-daemon">Marten Async Daemon</a> does fancy stuff to tune it depending on how fast new messages are appended. But still, it’s an approximation.</li> <li>polling may require more resources and have more extensive memory pressure.</li> <li>it’s not as easy as it seems to scale it in a reliable and resilient way (e.g. have multiple nodes, <a href="https://jeremydmiller.com/2020/05/05/using-postgresql-advisory-locks-for-leader-election/">leaders election</a> etc.),</li> <li>may be harder to implement than it seems, check more in <a href="/en/ordering_in_postgres_outbox/">How Postgres sequences issues can impact your messaging guarantees</a>.</li> </ul> <p>It would be great if we could be notified by the database when new messages arrive, right?</p> <p>If you’re using Postgres, then you may be the lucky person then!</p> <h2 id="postgres-logical-replication" style="position:relative;"><a href="#postgres-logical-replication" aria-label="postgres logical replication permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Postgres Logical Replication</h2> <p><strong>I’m not sure if you know, but <a href="/en/relational_databases_are_event_stores/">Relational databases work like event stores</a>. They have a concept called <em>“Write-Ahead Log”</em> (WAL).</strong> It is an append-only structure that records all the operations during transaction processing (Inserts, Updates, Deletes). When we commit a transaction, the data is firstly appended to the Write-Ahead Log. Then all operations are applied to tables, indexes, etc. Hence the name “Write-Ahead”: from this writing data to the log in advance of other changes. So from that perspective, tables and indexes are just read models for Write-Ahead Log.</p> <p>Postgres is a rock-solid database with many superb features. One of them is JSON support we’re using in <a href="https://martendb.io">Marten</a>, and the other is logical replication that we’ll look closer at now.</p> <p><strong>Logical replication takes the traditional approach to the next level. Instead of sending the raw binary stream of backed-up database files, we’re sending a stream of changes that were recorded in the Write-Ahead Log.</strong> It’s named logical, as it understands the operations’ semantics, plus the information about the tables it’s replicating. It’s highly flexible; it can be defined for one or multiple tables, filter records and copy a subset of data. It can inform you about changes to specific records. Thus it requires the replicated table to have primary keys.</p> <p>The first step is to enable logical replication in Postgres config (<em>postgresql.conf</em>):</p> <div class="gatsby-highlight" data-language="toml"><pre class="language-toml"><code class="language-toml"><span class="token key property">wal_level</span> <span class="token punctuation">=</span> logical</code></pre></div> <p>If Docker compose, you can configure it as:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token key atrule">postgres</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> postgres<span class="token punctuation">:</span>14.5<span class="token punctuation">-</span>alpine <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5432:5432"</span> <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> POSTGRES_DB=postgres <span class="token punctuation">-</span> POSTGRES_PASSWORD=postgres <span class="token key atrule">command</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"postgres"</span> <span class="token punctuation">-</span> <span class="token string">"-c"</span> <span class="token punctuation">-</span> <span class="token string">"wal_level=logical"</span></code></pre></div> <p>Similarly to the Outbox tables described above, it has publish-subscribe semantics.</p> <p><strong>To define publication, we need to call the following SQL statement:</strong></p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> PUBLICATION outbox_pub <span class="token keyword">FOR</span> <span class="token keyword">TABLE</span> outbox<span class="token punctuation">;</span></code></pre></div> <p><strong>We also need to tell Postgres that it should retain Write-Ahead Log entries required to perform logical replications, even if replicas/subscribers are disconnected from the database. We do that by defining logical replication slot:</strong></p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> pg_create_logical_replication_slot<span class="token punctuation">(</span><span class="token string">'outbox_slot'</span><span class="token punctuation">,</span> <span class="token string">'pgoutput'</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The first parameter is just a freehand name. The other is the name of the logical decoding plugin we’d like to use. Logical replication is still performed with binary data of the defined format. It’s not super-readable for humans or even for other systems. To be able to translate the replication format, we can use logical decoding plugins. They’re plenty of them, from the most known; let me mention:</p> <ul> <li><a href="https://www.postgresql.org/docs/current/test-decoding.html">test-decoding</a> - allows to read replication data in the SQL format. A built-in one; as the name suggests, is made for quick tests.</li> <li><a href="https://github.com/eulerto/wal2json">wal2json</a> - translates WAL changeset into JSON format.</li> <li><a href="https://www.postgresql.org/docs/15/logical-replication-architecture.html">pg_output</a> - standard decoding plugin.</li> </ul> <p><em>pg_output</em> is probably the most popular. It’s used in the most known and mature tool <a href="https://debezium.io/documentation/reference/stable/postgres-plugins.html">Debezium</a>. It’s also used in <a href="https://www.npgsql.org/doc/replication.html">Npgsql</a>, .NET Postgres data provider. We’ll use it to show logical replication in practice.</p> <h2 id="net-logical-replication-example" style="position:relative;"><a href="#net-logical-replication-example" aria-label="net logical replication example permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>.NET logical replication example</h2> <p>Let’s say we’re using a simplified version of the Publication table presented above:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> outbox <span class="token punctuation">(</span> position BIGSERIAL <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span><span class="token punctuation">,</span> event_type <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">250</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">data</span> JSONB <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Let’s start to shape the general API. Our subscription could look like this:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IEventsSubscription</span> <span class="token punctuation">{</span> <span class="token return-type class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> <span class="token function">Subscribe</span><span class="token punctuation">(</span><span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">EventsSubscriptionOptions</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> ConnectionString<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> SlotName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> PublicationName <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We just have a single method that takes the subscription options and <em>CancellationToken</em> to be able to stop the subscription. Subscription returns <em>IAsyncEnumerable</em>, which is a .NET interface for getting the asynchronous stream of notifications. It suits well, the publish-subscribe semantics. We’ll get a stream of messages, as they may have various CLR types, and then we need to use the lowest common denominator: base <em>object</em> class. Subscriptions options are used to pass the connection string to the Postgres database plus slot and publication name to know what we’re subscribing.</p> <p>Let’s now discuss the details of implementing our <em>Subscribe</em> method. The first thing is to open the connection and get the replication slot. We do that by:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> slotName<span class="token punctuation">,</span> publicationName<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> conn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">LogicalReplicationConnection</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> slot <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationSlot</span><span class="token punctuation">(</span>slotName<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Having that, we can start a subscription:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> message <span class="token keyword">in</span> conn<span class="token punctuation">.</span><span class="token function">StartReplication</span><span class="token punctuation">(</span>slot<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationOptions</span><span class="token punctuation">(</span>publicationName<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// TODO: PLACE PROCESSING LOGIC HERE</span> <span class="token comment">// Always call SetReplicationStatus() or assign LastAppliedLsn and LastFlushedLsn individually</span> <span class="token comment">// so that Npgsql can inform the server which WAL files can be removed/recycled.</span> conn<span class="token punctuation">.</span><span class="token function">SetReplicationStatus</span><span class="token punctuation">(</span>message<span class="token punctuation">.</span>WalEnd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// send the ACK to Postgres that we processed message</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">SendStatusUpdate</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We can use an asynchronous <em>foreach</em> loop. It will run infinitely until the connection with Postgres is down, or we cancel the subscription through <em>CancellationToken</em>. It will run for each replication message notification we get from Postgres. <em>pg_output</em> produces <a href="https://github.com/npgsql/npgsql/tree/main/src/Npgsql/Replication/PgOutput/Messages">multiple type of messages</a>. As our outbox table is append-only, let’s focus only on the insert part. Our full class implementation will look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventsSubscription</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventsSubscription</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">IAsyncEnumerable<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> <span class="token function">Subscribe</span><span class="token punctuation">(</span><span class="token class-name">EventsSubscriptionOptions</span> options<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">EnumeratorCancellation</span></span><span class="token punctuation">]</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>connectionString<span class="token punctuation">,</span> slotName<span class="token punctuation">,</span> publicationName<span class="token punctuation">)</span> <span class="token operator">=</span> options<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> conn <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">LogicalReplicationConnection</span><span class="token punctuation">(</span>connectionString<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> slot <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationSlot</span><span class="token punctuation">(</span>slotName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> message <span class="token keyword">in</span> conn<span class="token punctuation">.</span><span class="token function">StartReplication</span><span class="token punctuation">(</span>slot<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PgOutputReplicationOptions</span><span class="token punctuation">(</span>publicationName<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>message <span class="token keyword">is</span> <span class="token class-name">InsertMessage</span> insertMessage<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> <span class="token keyword">await</span> InsertMessageHandler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>insertMessage<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> conn<span class="token punctuation">.</span><span class="token function">SetReplicationStatus</span><span class="token punctuation">(</span>message<span class="token punctuation">.</span>WalEnd<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> conn<span class="token punctuation">.</span><span class="token function">SendStatusUpdate</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’ll cede the message handling to the dedicated <em>InsertMessageHandler</em>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">InsertMessageHandler</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">InsertMessage</span> message<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> columnNumber <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> eventTypeName <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> <span class="token keyword">value</span> <span class="token keyword">in</span> message<span class="token punctuation">.</span>NewRow<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>columnNumber<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token number">1</span><span class="token punctuation">:</span> eventTypeName <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetTextReader</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ReadToEndAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token number">2</span> <span class="token keyword">when</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetDataTypeName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLower</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token string">"jsonb"</span><span class="token punctuation">:</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> eventType <span class="token operator">=</span> Type<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>eventType <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>eventType<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">await</span> JsonSerialization<span class="token punctuation">.</span><span class="token function">FromJsonAsync</span><span class="token punctuation">(</span>eventType<span class="token punctuation">,</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">GetStream</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> @<span class="token keyword">event</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> columnNumber<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"You should not get here"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’ll need to iterate through all the columns in the insert message. We assume that our subscription will get messages from our standardised outbox table. We need to get the message type from the second column and use it to deserialise the method. We assume that we’ll have the assembly-qualified name of the CLR type (Is it the best technique? In the long term, you may need to think about <a href="/en/simple_events_versioning_patterns/">messages versioning</a>, but that’s a different story). Having deserialised the message, we can return it and propagate it to the subscriber.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> subscriptionOptions <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventsSubscriptionOptions</span><span class="token punctuation">(</span>ConnectrionString<span class="token punctuation">,</span> <span class="token string">"outbox_slot"</span><span class="token punctuation">,</span> <span class="token string">"outbox_pub"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> subscription <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventsSubscription</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> readEvent <span class="token keyword">in</span> subscription<span class="token punctuation">.</span><span class="token function">Subscribe</span><span class="token punctuation">(</span>subscriptionOptions<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Handle upcoming event here, e.g.</span> <span class="token comment">// eventBus.Publish(readEvent);</span> <span class="token punctuation">}</span></code></pre></div> <p>What to do with the event? You can <a href="/en/how_to_build_simple_event_pipeline/">pipe it to in-memory handler</a>, <a href="/en/integrating_Marten/">forward it to message bus</a>, in general, do what your imagination and sanity allows you.</p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p><strong>Postgres invests a lot in each release to make logical replication the most robust.</strong> See what they did in <a href="https://www.postgresql.fastware.com/blog/how-postgresql-15-improved-communication-in-logical-replication">just released 15 version</a>. Did I use it on production? Not yet. But I’m planning to.</p> <p>Some time ago, I did a <a href="https://github.com/oskardudycz/kafka-connect">proof of concept using Debezium to forward events from Marten to Kafka</a>. Now I’d like to step forward.</p> <p>I started to play with logical replication, and this article summarises my current findings. You can find more in my <a href="https://github.com/oskardudycz/PostgresOutboxPatternWithCDC.NET">GitHub repository</a>. I’d like to provide a simple, focused on, append-only outbox solution for .NET. Then do extensive tests to see if I could make it production-ready. Maybe incorporate that in Marten or generalise it into some tool if that works out.</p> <p>If you’re wondering on how existing records will be processed, read more in <a href="/en/how_to_get_all_messages_through_postgres_logical_replication">How to get all messages through Postgres logical replication</a>.</p> <p>Read also <a href="/en/postgres_superpowers/">Postgres Superpowers in Practice</a> where I explained the use case of Fleet Management and how it can help together with plugins like Timescale and PostGIS to build the real platform that detects the anomalies and pushes the notifications through #dotnet application to React app!</p> <p>What are the next steps? I need to check:</p> <ul> <li>subscriptions and replies,</li> <li>scenarios around backups and, in general, fault tolerance.</li> </ul> <p>That sounds like a decent pet project, so stay tuned; more to come in the future!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Testing business logic in Event Sourcing, and beyond!]]>https://event-driven.io/en/testing_event_sourcing/https://event-driven.io/en/testing_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a0d2d81822d9c122512c41765977ae13/8299d/2022-10-07-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMEAv/EABUBAQEAAAAAAAAAAAAAAAAAAAAC/9oADAMBAAIQAxAAAAG1dKoug0H/xAAbEAABBAMAAAAAAAAAAAAAAAADAAECESExMv/aAAgBAQABBQImZTayPJ0TkW6X/8QAFhEBAQEAAAAAAAAAAAAAAAAAACFB/9oACAEDAQE/AcR//8QAFREBAQAAAAAAAAAAAAAAAAAAACH/2gAIAQIBAT8BV//EABoQAAICAwAAAAAAAAAAAAAAAAECEDEAERL/2gAIAQEABj8CTrd4jgUYaf/EABsQAQACAwEBAAAAAAAAAAAAAAEAESExQXGR/9oACAEBAAE/Ia1iBOsHsQKXQGa7APIPgwua84Sivs//2gAMAwEAAgADAAAAEKQv/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERIf/aAAgBAwEBPxBTQh//xAAXEQADAQAAAAAAAAAAAAAAAAAAAREh/9oACAECAQE/ENsGP//EAB0QAQACAgIDAAAAAAAAAAAAAAEAESExQWFxgcH/2gAIAQEAAT8Q0QIFqDEFeldLBt1AQXEuG+S98P2OItiqDZC4nKf/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/a0d2d81822d9c122512c41765977ae13/c60e9/2022-10-07-cover.jpg" srcset="/static/a0d2d81822d9c122512c41765977ae13/37402/2022-10-07-cover.jpg 200w, /static/a0d2d81822d9c122512c41765977ae13/4cda9/2022-10-07-cover.jpg 400w, /static/a0d2d81822d9c122512c41765977ae13/c60e9/2022-10-07-cover.jpg 800w, /static/a0d2d81822d9c122512c41765977ae13/6c738/2022-10-07-cover.jpg 1200w, /static/a0d2d81822d9c122512c41765977ae13/56dca/2022-10-07-cover.jpg 1600w, /static/a0d2d81822d9c122512c41765977ae13/8299d/2022-10-07-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I’ve heard a few times that Event Sourcing is hard to test. I’m not sure where this myth comes from;</strong> maybe from mixing it with <a href="/en/event_streaming_is_not_event_sourcing/">event streaming</a>. Event Sourcing logic is pretty straightforward. We’re getting the list of events recorded for a specific entity (e.g. bank account), building the current state from them and running business logic based on it. As a result, we’re getting either a new event or a list of events.</p> <p>Let’s take a bank account as an example. We could define it as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">BankAccount</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">BankAccountStatus</span> Status<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Balance <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">BankAccount</span> <span class="token function">Evolve</span><span class="token punctuation">(</span> <span class="token class-name">BankAccount</span> bankAccount<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span> <span class="token punctuation">)</span> <span class="token operator">=></span> @<span class="token keyword">event</span> <span class="token keyword">switch</span> <span class="token punctuation">{</span> <span class="token return-type class-name">BankAccountOpened</span> bankAccountCreated <span class="token operator">=></span> <span class="token function">Create</span><span class="token punctuation">(</span>bankAccountCreated<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">DepositRecorded</span> depositRecorded <span class="token operator">=></span> bankAccount<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>depositRecorded<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">CashWithdrawnFromATM</span> cashWithdrawnFromATM <span class="token operator">=></span> bankAccount<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>cashWithdrawnFromATM<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">BankAccountClosed</span> bankAccountClosed <span class="token operator">=></span> bankAccount<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>bankAccountClosed<span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> bankAccount <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">BankAccount</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">BankAccountOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BankAccount</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>BankAccountId<span class="token punctuation">,</span> BankAccountStatus<span class="token punctuation">.</span>Opened<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Version<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name">BankAccount</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">DepositRecorded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> Balance <span class="token operator">=</span> Balance <span class="token operator">+</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Amount <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name">BankAccount</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">CashWithdrawnFromATM</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> Balance <span class="token operator">=</span> Balance <span class="token operator">-</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Amount <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name">BankAccount</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">BankAccountClosed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> BankAccountStatus<span class="token punctuation">.</span>Closed <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It contains the state definition (<em>Id</em>, <em>Status</em>, and <em>Balance</em>) plus <em>Evolve</em> function that takes the current state, applies the event, and gets the new state. Read more in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events</a>.</p> <p>The simplest example of how to run business logic might look like this (using C#):</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">BankAccountService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">CashWithdrawnFromATM</span> <span class="token function">Handle</span><span class="token punctuation">(</span> <span class="token class-name">WithdrawnCashFromATM</span> command<span class="token punctuation">,</span> <span class="token class-name">BankAccount</span> account <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>account<span class="token punctuation">.</span>Status <span class="token operator">==</span> BankAccountStatus<span class="token punctuation">.</span>Closed<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Account is closed!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>account<span class="token punctuation">.</span>Balance <span class="token operator">&lt;</span> command<span class="token punctuation">.</span>Amount<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Not enough money!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashWithdrawnFromATM</span><span class="token punctuation">(</span>account<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Amount<span class="token punctuation">,</span> command<span class="token punctuation">.</span>AtmId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see, it’s a pure function that takes the <em>CashWithdrawnFromATM</em> command (representing user intention), the current bank account state and returns a new fact: an event that the cash was withdrawn. It’s a pure function that’s not causing any side effects. Thus, straightforward code to test.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenOpenBankAccountWithEnoughMoney_WhenCashWithdrawnFromATM_ThenSucceeds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Given</span> <span class="token comment">// Existing events</span> <span class="token class-name"><span class="token keyword">var</span></span> bankAccountId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> accountNumber <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> currencyISOCode <span class="token operator">=</span> <span class="token string">"USD"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> accountOpened <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BankAccountOpened</span><span class="token punctuation">(</span> bankAccountId<span class="token punctuation">,</span> accountNumber<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> currencyISOCode <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> cashierId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> initialAmount <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">decimal</span><span class="token punctuation">)</span>random<span class="token punctuation">.</span><span class="token function">NextDouble</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">*</span> <span class="token number">100</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> depositRecorded <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DepositRecorded</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> amount<span class="token punctuation">,</span> cashierId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// and currentstate build from them</span> <span class="token class-name"><span class="token keyword">var</span></span> currentState <span class="token operator">=</span> <span class="token function">GetCurrentState</span><span class="token punctuation">(</span> accountOpened<span class="token punctuation">,</span> depositRecorded <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// When</span> <span class="token class-name"><span class="token keyword">var</span></span> withdrawalAmount <span class="token operator">=</span> initialAmount <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> atmId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Command is handled</span> <span class="token class-name"><span class="token keyword">var</span></span> newEvent <span class="token operator">=</span> BankAccountService<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">WithdrawCashFromATM</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> withdrawalAmount<span class="token punctuation">,</span> atmId<span class="token punctuation">)</span><span class="token punctuation">,</span> currentState <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Then</span> Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashWithdrawnFromATM</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> withdrawalAmount<span class="token punctuation">,</span> atmId<span class="token punctuation">)</span><span class="token punctuation">,</span> newEvent <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">BankAccount</span> <span class="token function">GetCurrentState</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> currentState <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BankAccount</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> currentState <span class="token operator">=</span> BankAccount<span class="token punctuation">.</span><span class="token function">Evolve</span><span class="token punctuation">(</span>currentState<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> currentState <span class="token punctuation">}</span></code></pre></div> <p>We assert that the specific event is returned after running the command handler logic for the current state.</p> <p><strong>The tests are written in the following pattern:</strong></p> <p><strong>Given</strong> the sequence of past events.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ad5dead0c1935be988a50bc42261875b/a331c/2022-10-07-logic-1.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAB8ElEQVQozz2RS08bQRCE99/nxoH8CITACiKCHBwTRQgUIgh2AL/YBe/D7O5M9/RMz2PX5hKNkSLVYdRST1V/lRB3QtkWuZZ6mRXlmxj9uDw4HBwenwwvLo8Gpyen58PRz4PDwc3dpKqxrAHIKe1Q+6QBroGlsgJNLU2LTNxZt2W3acGUNVaNqqWuaszXMq/EuqUGWCADuUQoK8mB6SVvWgpIblaJ8/Fs+JSmQpPppHItWkkeTUe2R+2Fcq00QtkEyAvlRZXjagZ1abi/K5rPo6v9b6PbZaq5F8pJ8pKcQBu1MxMYwybKBODNevz94ejT7PormE6Z4MO2FfpusloL0rZTHIfabW14127D4V3bXpJLUHvgvh5fPJzsXY+OGzDxY2XJ9VkuZs9t1UQrsv3zSzFdpK/F2zxdLbNCoE1a4Dd05f3F9Mve8vdQ6Ai/wYgkK+TDgm7+vKxbkuSny9fx42KRFU/zl2W6W5bkGgpiepWd7c9/nSH38RAd0PgG7OQxvb3/CzpI8op77bYfsY3tI20ZMXDdyLysslUulN3VbhswDXAbCfkWXWxUmg/FqsBE56ymsgEBhrgj7mSE70B7xUFxwJjCg/Zo4jsOTQAT5O6uJBd617sB48F0yP8VkDtle82dcRtt+8j8Y2471F5q/w9ngFRno9zRcQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="given" title="given" src="/static/ad5dead0c1935be988a50bc42261875b/a331c/2022-10-07-logic-1.png" srcset="/static/ad5dead0c1935be988a50bc42261875b/36ca5/2022-10-07-logic-1.png 200w, /static/ad5dead0c1935be988a50bc42261875b/a3397/2022-10-07-logic-1.png 400w, /static/ad5dead0c1935be988a50bc42261875b/a331c/2022-10-07-logic-1.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>When</strong> the business logic is run for the command and the current state built from events.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b31868b2078474d069390e9d10d1fafd/a331c/2022-10-07-logic-2.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACCElEQVQozzVSTU/bQBD1/z/0QqX23AuilAqogKKQRhWqFEQhpPmOYxNv7Oz37uyX14RKrZyq0ju8kWbePL2ZBFzk2lFpMYdljjZbdtO7Pfx4cvTptNO7Pf58fnp+0el+Pzw6uXt42mBZYsG1lxAkhIQIi7nlyrN23lBptY3O7ax7ocK03URhDiWWRcWLkm2pJsIyaYX2CVOe6yAgcqip8lL7Wckun2adSbpioE3kylPpuA7SROUaCYEpT7lhyiVCtwWnRNFKMGps84DIQffHwdXNz0UKttmr79G6c0y5PXdcu0SZKEzN0ZLM78pszMFrE0O9IxQehs9bpsHVykZlooRgws6EF+NfxF4ukVALCKLKaPZrORtipkRrzIFvsoJNU1qSdomEoGy9zIvVerNaV0K1RhIqLBYOLweof47mj5Rxno0Jt1TaDPHRQt89ZiVRAupZur782r26/nbd6U2maTvMtSc6ssltevZm3r+g0mXDfj7sF3lKVD0cp/eDUZ4WjAKiel1xRFW2oc8It6dqM5AWE16UVbZG/8LHHLZME2EYNDgdZl/errvHz+MF3uBiNGM5AhOpdElONCKCCaNt1O3DBKa9NLWytTJBuh0tVnn/YnLfqzZYz9PB+3eDww9FgdrACmYqqqkwwgRhamnjf7Rc2Qj+1TV/wP/W9auEwDmMZtPpciFN8xeoFk5mYvHkUgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="when" title="when" src="/static/b31868b2078474d069390e9d10d1fafd/a331c/2022-10-07-logic-2.png" srcset="/static/b31868b2078474d069390e9d10d1fafd/36ca5/2022-10-07-logic-2.png 200w, /static/b31868b2078474d069390e9d10d1fafd/a3397/2022-10-07-logic-2.png 400w, /static/b31868b2078474d069390e9d10d1fafd/a331c/2022-10-07-logic-2.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Then</strong> the following set of events is returned (when succeeded), or an exception is thrown (when failed).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8699ff6478415274de412750a9b16fa5/a331c/2022-10-07-logic-3.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACCUlEQVQozyWSy28TQQzG9/8/cIAzXKsCLagSRWpTpaEqgqoLbbNtmn1kX7PZGc943tlJwgG0RfoOtmXL8vdzpGwA6Rjanuu0aNo1XFxeHbw/Ovz4+Xx69eH45PPJl/OL2cHh0c/4vu2x7YVQXmov9CaiwvbCcukBXS8MQ6tscH5v/Y4J0/ZIqOy5Jj3WHdQEOqaosICWKx+BdFxthA5cD0x6VH5B4PRucf6Y56CVCSA9w7EHTVB2i3ozVrgB6SKuxoQziqwTwIzdxnX/ZnL1+nRyu8y13QJ6rkYxdCDdyzI/bpYukiYIPYg2o8sbskq48srthv1fJnx8X65BaRekDdIM2u+M32m3M36vR6d8hHoj9GYczuL06ddaWCDV+vcE66ei4U8ZI9SBctIMedkWFakIzSuyqjpUQ8SEXQsDTQZpvC6fuf1TJzfJ0av55XFWw3ypf8QFRVd1/Gwy+3o2vfz2fTq7ns6u01U73jz61K0gi5s8QRPKPGvTpHx+6Jh+eCxu7+YkLZZ385JAQ7GmourFquoqQiMuHROGAnag6pb8ZwBoGVoqLBOWq03XcZKWzSLtStIkC1ZUUg9cbaIVVU3Pe0pRGYFqBC7HB0AzSDugHsbYBul2PdfsPnl+9zb/dEw6yqWPKjAv3A03I0m0QdiAdnhRkG6r3db4rbZB+T2aAKAIFUxooYd/culPOSUIWqIAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="then" title="then" src="/static/8699ff6478415274de412750a9b16fa5/a331c/2022-10-07-logic-3.png" srcset="/static/8699ff6478415274de412750a9b16fa5/36ca5/2022-10-07-logic-3.png 200w, /static/8699ff6478415274de412750a9b16fa5/a3397/2022-10-07-logic-3.png 400w, /static/8699ff6478415274de412750a9b16fa5/a331c/2022-10-07-logic-3.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>The test is written in the <a href="https://en.wikipedia.org/wiki/Behavior-driven_development">Behaviour Driven-Development</a> style.</p> <p>The pattern is repeatable, and we could use it for all possible event-sourcing business logic.</p> <p><strong>Wouldn’t it be great to be able to generalise business logic tests as below?</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">BankAccountTests</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Random</span> random <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">DeciderSpecification<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">,</span> BankAccount<span class="token punctuation">></span></span> Spec <span class="token operator">=</span> Specification<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>BankAccount<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> BankAccountDecider<span class="token punctuation">.</span>Handle<span class="token punctuation">,</span> BankAccount<span class="token punctuation">.</span>Evolve <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenNonExistingBankAccount_WhenOpenWithValidParams_ThenSucceeds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> bankAccountId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> accountNumber <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> currencyISOCode <span class="token operator">=</span> <span class="token string">"USD"</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">OpenBankAccount</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> accountNumber<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> currencyISOCode<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">BankAccountOpened</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> accountNumber<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> currencyISOCode<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenOpenBankAccount_WhenRecordDepositWithValidParams_ThenSucceeds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> bankAccountId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> amount <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">decimal</span><span class="token punctuation">)</span>random<span class="token punctuation">.</span><span class="token function">NextDouble</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> cashierId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token function">BankAccountOpened</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">RecordDeposit</span><span class="token punctuation">(</span>amount<span class="token punctuation">,</span> cashierId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">DepositRecorded</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> amount<span class="token punctuation">,</span> cashierId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenClosedBankAccount_WhenRecordDepositWithValidParams_ThenFailsWithInvalidOperationException</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> bankAccountId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> amount <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">decimal</span><span class="token punctuation">)</span>random<span class="token punctuation">.</span><span class="token function">NextDouble</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> cashierId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span> <span class="token function">BankAccountOpened</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BankAccountClosed</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">RecordDeposit</span><span class="token punctuation">(</span>amount<span class="token punctuation">,</span> cashierId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ThenThrows</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>InvalidOperationException<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>exception <span class="token operator">=></span> exception<span class="token punctuation">.</span>Message<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token string">"Account is closed!"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenOpenBankAccountWithEnoughMoney_WhenCashWithdrawnFromATM_ThenSucceeds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> bankAccountId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> initialAmount <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">decimal</span><span class="token punctuation">)</span>random<span class="token punctuation">.</span><span class="token function">NextDouble</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token operator">*</span> <span class="token number">100</span> <span class="token operator">+</span> <span class="token number">10</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> withdrawalAmount <span class="token operator">=</span> initialAmount <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> atmId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span> <span class="token function">BankAccountOpened</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">)</span> <span class="token function">DepositRecorded</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> initialAmount<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">WithdrawCashFromATM</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> withdrawalAmount<span class="token punctuation">,</span> atmId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashWithdrawnFromATM</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> amount<span class="token punctuation">,</span> cashierId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">BankAccountOpened</span> <span class="token function">BankAccountOpened</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> bankAccountId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> accountNumber <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> currencyISOCode <span class="token operator">=</span> <span class="token string">"USD"</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BankAccountOpened</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> accountNumber<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> currencyISOCode<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">BankAccountOpened</span> <span class="token function">DepositRecorded</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> bankAccountId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> amount<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> cashierId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DepositRecorded</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> amount<span class="token punctuation">,</span> cashierId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">BankAccountClosed</span> <span class="token function">BankAccountClosed</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> bankAccountId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> reason <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BankAccountClosed</span><span class="token punctuation">(</span>bankAccountId<span class="token punctuation">,</span> reason<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Ok, but how to get to that?</p> <p><strong>No worries, I got you covered!</strong></p> <p>Some time ago <a href="/en/ogooreck_sneaky_bdd_testing_framework/">I created Ogooreck, a sneaky testing library in BDD style</a>.</p> <p><strong>Now I extended it also for the business logic tests</strong>. It’s testing framework agnostic so that you can use it both for XUnit, NUnit and others! Just <a href="https://www.nuget.org/packages/Ogooreck">add the NuGet</a>, and you’ll be able to test your business logic as described in this article. All of that (and more) is already available. See more in the <a href="https://github.com/oskardudycz/Ogooreck#business-logic-testing">Ogooreck repository</a>.</p> <p>Are you not coding in .NET? Don’t you want to use my library? That’s also fine. Let me try to explain how to build it on your own.</p> <h2 id="decider-tests-specification" style="position:relative;"><a href="#decider-tests-specification" aria-label="decider tests specification permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Decider tests specification</h2> <p><strong>There are multiple ways how to run business logic.</strong> I showed them in <a href="/en/slim_your_entities_with_event_sourcing/">Slim your aggregates with Event Sourcing!</a>. <strong>Let’s start today with the pattern called <em>Decider</em>.</strong> It’s a <a href="https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider">pattern defined by Jérémie Chassaing</a>, I explained it in detail in <a href="/en/how_to_effectively_compose_your_business_logic/">How to effectively compose your business logic</a>. Decider pattern clarifies how to run the business logic. It’s composed of the following three methods:</p> <ul> <li><strong>decide</strong> - a method that’s running the business logic as in the example shown above in the incident categorisation example,</li> <li><strong>evolve</strong> - a method that takes the current state, an event and returns the new state updated by event data,</li> <li><strong>get initial state</strong> - a method that returns the default state object, this methods is used as a base for <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">getting the current state from events</a>.</li> <li>there’s also the terminal function, but let’s skip it for now. If you’re interested, read more in <a href="https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider">Jérémie’s article</a></li> </ul> <p>We could describe the decider in C# code as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token generic-method"><span class="token function">Decider</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">></span></span> Decide<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> Evolve<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span></span> GetInitialState <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>I like it, as it’s a concise and complete way of explaining the event-driven business logic. It helps in composability.</p> <p>The handling could be described as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">private</span> <span class="token return-type class-name"><span class="token punctuation">(</span>TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> TState<span class="token punctuation">)</span></span> <span class="token generic-method"><span class="token function">Handle</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> events<span class="token punctuation">,</span> <span class="token class-name">Command</span> command <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> currentState <span class="token operator">=</span> decider<span class="token punctuation">.</span><span class="token function">GetInitialState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newEvents <span class="token operator">=</span> decider<span class="token punctuation">.</span><span class="token function">Decide</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> currentState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newState <span class="token operator">=</span> newEvents<span class="token punctuation">.</span><span class="token function">Aggregate</span><span class="token punctuation">(</span>currentState<span class="token punctuation">,</span> decider<span class="token punctuation">.</span>Evolve<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>newEvents<span class="token punctuation">,</span> newState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>For defined above <em>BankAccount</em> entity the <em>Decide</em> method could look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">BankAccountDecider</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">object</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">object</span></span> command<span class="token punctuation">,</span> <span class="token class-name">BankAccount</span> bankAccount <span class="token punctuation">)</span> <span class="token operator">=></span> command <span class="token keyword">switch</span> <span class="token punctuation">{</span> <span class="token return-type class-name">OpenBankAccount</span> openBankAccount <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>openBankAccount<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">RecordDeposit</span> recordDeposit <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>recordDeposit<span class="token punctuation">,</span> bankAccount<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">WithdrawnCashFromATM</span> withdrawnCash <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>withdrawnCash<span class="token punctuation">,</span> bankAccount<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token return-type class-name">CloseBankAccount</span> closeBankAccount <span class="token operator">=></span> <span class="token function">Handle</span><span class="token punctuation">(</span>closeBankAccount<span class="token punctuation">,</span> bankAccount<span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">command<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name</span><span class="token punctuation">}</span></span><span class="token string"> cannot be handled for Bank Account"</span></span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">BankAccountOpened</span> <span class="token function">Handle</span><span class="token punctuation">(</span> <span class="token class-name">OpenBankAccount</span> command <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BankAccountOpened</span><span class="token punctuation">(</span> command<span class="token punctuation">.</span>BankAccountId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>AccountNumber<span class="token punctuation">,</span> command<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>CurrencyISOCode <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">DepositRecorded</span> <span class="token function">Handle</span><span class="token punctuation">(</span> <span class="token class-name">RecordDeposit</span> command<span class="token punctuation">,</span> <span class="token class-name">BankAccount</span> account <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>account<span class="token punctuation">.</span>Status <span class="token operator">==</span> BankAccountStatus<span class="token punctuation">.</span>Closed<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Account is closed!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DepositRecorded</span><span class="token punctuation">(</span>account<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Amount<span class="token punctuation">,</span> command<span class="token punctuation">.</span>CashierId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">CashWithdrawnFromATM</span> <span class="token function">Handle</span><span class="token punctuation">(</span> <span class="token class-name">WithdrawnCashFromATM</span> command<span class="token punctuation">,</span> <span class="token class-name">BankAccount</span> account <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>account<span class="token punctuation">.</span>Status <span class="token operator">==</span> BankAccountStatus<span class="token punctuation">.</span>Closed<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Account is closed!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>account<span class="token punctuation">.</span>Balance <span class="token operator">&lt;</span> command<span class="token punctuation">.</span>Amount<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Not enough money!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashWithdrawnFromATM</span><span class="token punctuation">(</span>account<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Amount<span class="token punctuation">,</span> command<span class="token punctuation">.</span>AtmId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name">BankAccountClosed</span> <span class="token function">Handle</span><span class="token punctuation">(</span> <span class="token class-name">CloseBankAccount</span> command<span class="token punctuation">,</span> <span class="token class-name">BankAccount</span> account <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>account<span class="token punctuation">.</span>Status <span class="token operator">==</span> BankAccountStatus<span class="token punctuation">.</span>Closed<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Account is already closed!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">BankAccountClosed</span><span class="token punctuation">(</span>account<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Reason<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>To define the following test, we’ll need to mix a bit of <a href="https://en.wikipedia.org/wiki/Specification_pattern">Specification</a> and <a href="https://en.wikipedia.org/wiki/Builder_pattern">Builder</a> patterns.</p> <p>Let’s start with the general specification description:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">DeciderSpecification<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Decider<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> decider<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">DeciderSpecification</span><span class="token punctuation">(</span><span class="token class-name">Decider<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> decider<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>decider <span class="token operator">=</span> decider<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">WhenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Given</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> events<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> currentState <span class="token operator">=</span> decider<span class="token punctuation">.</span><span class="token function">GetInitialState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> events<span class="token punctuation">.</span><span class="token function">Aggregate</span><span class="token punctuation">(</span>currentState<span class="token punctuation">,</span> decider<span class="token punctuation">.</span>Evolve<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">WhenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Given</span><span class="token punctuation">(</span><span class="token class-name">TState</span> currentState<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> currentState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">WhenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>decider<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">WhenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Given</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span></span> getCurrentState<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>decider<span class="token punctuation">,</span> getCurrentState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>It’s a simple wrapper that takes the <em>Decider</em> instance and generates a method to get the current state. This method will either take an entity instance or build it from the provided events sequence.</strong></p> <p>We could also add helper methods to facilitate Specification setup:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">DeciderSpecification<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Decider<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> decider <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>decider<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">DeciderSpecification<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">></span></span> decide<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> evolve<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">?</span></span> getInitialState <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TState</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token operator">=></span> <span class="token function">For</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Decider</span><span class="token punctuation">(</span> decide<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> getInitialState <span class="token operator">??</span> ObjectFactory<span class="token operator">&lt;</span>TState<span class="token operator">></span><span class="token punctuation">.</span>GetDefaultOrUninitialized <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">DeciderSpecification<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span> decide<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> evolve<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">?</span></span> getInitialState <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span> <span class="token function">decide</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> getInitialState <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Nothing spectacular here besides the <em>ObjectFactory<TState>.GetDefaultOrUninitialized</em> method. It’s a simple wrapper that tries to create an instance with a default constructor or creates an uninitialised object.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ObjectFactory<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">T</span> <span class="token function">GetUnitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>T<span class="token punctuation">)</span>RuntimeHelpers<span class="token punctuation">.</span><span class="token function">GetUninitializedObject</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">T</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">T<span class="token punctuation">?</span></span> <span class="token function">GetDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>T<span class="token punctuation">?</span><span class="token punctuation">)</span>Activator<span class="token punctuation">.</span><span class="token function">CreateInstance</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">T</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">MissingMethodException</span><span class="token punctuation">?</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">default</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">T</span> <span class="token function">GetDefaultOrUninitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">GetDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token function">GetUnitialized</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We now have the basic test input setup; we should define the test run. We’ll do that via <em>WhenDeciderSpecificationBuilder</em>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WhenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Decider<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> decider<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">?</span></span> getCurrentState<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">WhenDeciderSpecificationBuilder</span><span class="token punctuation">(</span> <span class="token class-name">Decider<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> decider<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">?</span></span> getCurrentState <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>decider <span class="token operator">=</span> decider<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>getCurrentState <span class="token operator">=</span> getCurrentState<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">When</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">TCommand<span class="token punctuation">[</span><span class="token punctuation">]</span></span> commands<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token function">RunTest</span><span class="token punctuation">(</span>commands<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token return-type class-name">Lazy<span class="token punctuation">&lt;</span>TestResult<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">RunTest</span><span class="token punctuation">(</span><span class="token class-name">TCommand<span class="token punctuation">[</span><span class="token punctuation">]</span></span> commands<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> currentState <span class="token operator">=</span> <span class="token punctuation">(</span>getCurrentState <span class="token operator">??</span> decider<span class="token punctuation">.</span>GetInitialState<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> resultEvents <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> command <span class="token keyword">in</span> commands<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newEvents <span class="token operator">=</span> decider<span class="token punctuation">.</span><span class="token function">Decide</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> currentState<span class="token punctuation">)</span><span class="token punctuation">;</span> resultEvents<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span>newEvents<span class="token punctuation">)</span><span class="token punctuation">;</span> currentState <span class="token operator">=</span> newEvents<span class="token punctuation">.</span><span class="token function">Aggregate</span><span class="token punctuation">(</span>currentState<span class="token punctuation">,</span> decider<span class="token punctuation">.</span>Evolve<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TestResult<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span><span class="token punctuation">(</span>currentState<span class="token punctuation">,</span> resultEvents<span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token generic-method"><span class="token function">TestResult</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">TState</span> CurrentState<span class="token punctuation">,</span> <span class="token class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> NewEvents <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>We’re taking the decider instance and generating the lazily evaluated test run.</strong> It’s the same as the business handling defined above, with the distinction that we’re allowing to provide and run multiple commands. Typically, it’s best to add multiple tests instead of running multiple commands, yet sometimes you’d like to do some integration test or test command orchestration, which can be helpful.</p> <p><strong>The final thing that we need to provide is test verification.</strong> We’ll do that through <em>ThenDeciderSpecificationBuilder</em>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Lazy<span class="token punctuation">&lt;</span>TestResult<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span><span class="token punctuation">></span></span> getResult<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">ThenDeciderSpecificationBuilder</span><span class="token punctuation">(</span><span class="token class-name">Lazy<span class="token punctuation">&lt;</span>TestResult<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span><span class="token punctuation">></span></span> getResult<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>getResult <span class="token operator">=</span> getResult<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> expectedEvents<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> getResult<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> result<span class="token punctuation">.</span>NewEvents<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">BeEquivalentTo</span><span class="token punctuation">(</span>expectedEvents<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Then</span><span class="token punctuation">(</span><span class="token class-name">TState</span> expectedState<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> getResult<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> result<span class="token punctuation">.</span>CurrentState<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">BeEquivalentTo</span><span class="token punctuation">(</span>expectedState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">></span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> eventsAssertions<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> getResult<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> then <span class="token keyword">in</span> eventsAssertions<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">then</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>NewEvents<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> stateAssertions<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> getResult<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> then <span class="token keyword">in</span> stateAssertions<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">then</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>CurrentState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">></span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> assertions<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> getResult<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> then <span class="token keyword">in</span> assertions<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">then</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>CurrentState<span class="token punctuation">,</span> result<span class="token punctuation">.</span>NewEvents<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">ThenThrows</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TException<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Action<span class="token punctuation">&lt;</span>TException<span class="token punctuation">></span><span class="token punctuation">?</span></span> assert <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TException</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Exception</span></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> _ <span class="token operator">=</span> getResult<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">TException</span> e<span class="token punctuation">)</span> <span class="token punctuation">{</span> assert<span class="token punctuation">?.</span><span class="token function">Invoke</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Class wraps the lazily evaluated test run (to ensure that it’s run only once) and allows multiple assertions using fluent syntax. The test will be run on the first <em>Then</em> method call.</p> <h2 id="event-driven-aggregate-tests" style="position:relative;"><a href="#event-driven-aggregate-tests" aria-label="event driven aggregate tests permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Event-driven aggregate tests</h2> <p><strong>Testing Decider is cool, but what if we’re not so fancy and using good, old aggregates?</strong></p> <p>Let’s say that we have the classical <em>Aggregate</em> base class:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Aggregate</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Aggregate<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Version <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">NonSerialized</span></span><span class="token punctuation">]</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Queue<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> uncommittedEvents <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">virtual</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">When</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token function">DequeueUncommittedEvents</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> dequeuedEvents <span class="token operator">=</span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">Clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> dequeuedEvents<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Enqueue</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>And Shopping Cart aggregate defined with it:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> TotalPrice <span class="token operator">=></span> ProductItems<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>TotalPrice<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Open</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> cartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCart</span><span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Evolve</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> cart<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartOpened</span> opened<span class="token punctuation">:</span> cart<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>opened<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ProductAdded</span> productAdded<span class="token punctuation">:</span> cart<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>productAdded<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ProductRemoved</span> productRemoved<span class="token punctuation">:</span> cart<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>productRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartConfirmed</span> confirmed<span class="token punctuation">:</span> cart<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>confirmed<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartCanceled</span> canceled<span class="token punctuation">:</span> cart<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>canceled<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> cart<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> ShoppingCartOpened<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> clientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">;</span> ClientId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ClientId<span class="token punctuation">;</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddProduct</span><span class="token punctuation">(</span> <span class="token class-name">IProductPriceCalculator</span> productPriceCalculator<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Adding product for the cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> pricedProductItem <span class="token operator">=</span> productPriceCalculator<span class="token punctuation">.</span><span class="token function">Calculate</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> ProductAdded<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> pricedProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">MergeWith</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span> <span class="token class-name">PricedProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Removing product from the cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>UnitPrice</span><span class="token punctuation">}</span></span><span class="token string">' was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>existingProductItem<span class="token punctuation">.</span><span class="token function">HasEnough</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">existingProductItem<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> ProductRemoved<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItemToBeRemoved <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem<span class="token punctuation">.</span><span class="token function">HasTheSameQuantity</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">Subtract</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Confirming cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> ShoppingCartConfirmed<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Canceling cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> ShoppingCartCanceled<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Canceled<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">PricedProductItem<span class="token punctuation">?</span></span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> ProductItems <span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span><span class="token function">MatchesProductAndPrice</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Aggregate methods are not returning anything. They also do not have a command instance as a parameter, just standard method params. This is fine, as it’s not recommended to pass commands through aggregate methods, as by that, we’re tying the application layer with domain definition.</p> <p><strong>Can we test it with the test specifications defined above?</strong> Almost! We need to add a bit more code.</p> <p>In general, command handling doesn’t mean that we should use a command object. We can treat command handling as processing input parameters on the aggregate/entity object. We may also want to test the returned event and the updated aggregate state to ensure we didn’t forget to apply the newly created event to the state.</p> <p>To do that, let’s modify the Decider definition to allow returning both events collection and new state.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token generic-method"><span class="token function">Decider</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TState<span class="token punctuation">,</span> DecideResult<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">></span></span> Decide<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> Evolve<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span></span> GetInitialState <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token generic-method"><span class="token function">DecideResult</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> NewEvents<span class="token punctuation">,</span> <span class="token class-name">TState<span class="token punctuation">?</span></span> NewState <span class="token operator">=</span> <span class="token keyword">default</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DecideResult</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">DecideResult<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> newEvents<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>newEvents<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">DecideResult<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">TState</span> newState<span class="token punctuation">,</span> <span class="token keyword">params</span> <span class="token class-name">TEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> newEvents<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>newEvents<span class="token punctuation">,</span> newState<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Unfortunately, <a href="/en/union_types_in_csharp/">C# doesn’t have algebraic types</a>, so we need to work around it by adding the <em>DecideResult</em> that can take either just new events (for the decider scenario) or new events and new state.</p> <p><strong>Having that, let’s define the business logic handler template:</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">delegate</span> <span class="token return-type class-name">DecideResult<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Handler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">TState</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It’s a simple function template definition that will do some logic on the state and return the result.</p> <p><strong>And now, focus, as we’ll do a bit of magic now!</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">HandlerSpecification<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DeciderSpecification<span class="token punctuation">&lt;</span>Handler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">HandlerSpecification</span><span class="token punctuation">(</span><span class="token class-name">Decider<span class="token punctuation">&lt;</span>Handler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> decider<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>decider<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We also need to define the specification definition methods:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">HandlerSpecification<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>Handler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">,</span> TState<span class="token punctuation">,</span> DecideResult<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">></span></span> decide<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> evolve<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">?</span></span> getInitialState <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> Decider<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span> decide<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> getInitialState <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">HandlerSpecification<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">?</span></span> evolve <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">?</span></span> getInitialState <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span> Decider<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Handler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token named-parameter punctuation">decide</span><span class="token punctuation">:</span> <span class="token punctuation">(</span>handler<span class="token punctuation">,</span> currentState<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">handler</span><span class="token punctuation">(</span>currentState<span class="token punctuation">)</span><span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> getInitialState <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We defined the new test specification derived from the well-known <em>DeciderSpecification</em>. As the command parameter, we’re not providing the specific command class but a handler. As explained above, the handler will be a function that will take the current state and run defined logic on it.</p> <p><strong>What will the test look like?</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">AggregateTestExtensions<span class="token punctuation">&lt;</span>ShoppingCart<span class="token punctuation">></span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartTests</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Random</span> random <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">HandlerSpecification<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">,</span> ShoppingCart<span class="token punctuation">></span></span> Spec <span class="token operator">=</span> Specification<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">,</span> ShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>Handle<span class="token punctuation">,</span> ShoppingCart<span class="token punctuation">.</span>Evolve<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">class</span> <span class="token class-name">DummyProductPriceCalculator</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IProductPriceCalculator</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name"><span class="token keyword">decimal</span></span> price<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">DummyProductPriceCalculator</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">decimal</span></span> price<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>price <span class="token operator">=</span> price<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IReadOnlyList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> <span class="token function">Calculate</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span></span> productItems<span class="token punctuation">)</span> <span class="token operator">=></span> productItems<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> PricedProductItem<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>pi<span class="token punctuation">,</span> price<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenNonExistingShoppingCart_WhenOpenWithValidParams_ThenSucceeds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> shoppingCartId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> ShoppingCart<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenOpenShoppingCart_WhenAddProductWithValidParams_ThenSucceeds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> shoppingCartId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> <span class="token function">ValidProductItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> price <span class="token operator">=</span> random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> priceCalculator <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DummyProductPriceCalculator</span><span class="token punctuation">(</span>price<span class="token punctuation">)</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token function">ShoppingCartOpened</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>cart <span class="token operator">=></span> cart<span class="token punctuation">.</span><span class="token function">AddProduct</span><span class="token punctuation">(</span>priceCalculator<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductAdded</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> PricedProductItem<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>productItem<span class="token punctuation">,</span> price<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartOpened</span> <span class="token function">ShoppingCartOpened</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItem</span> <span class="token function">ValidProductItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> ProductItem<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">AggregateTestExtensions<span class="token punctuation">&lt;</span>TAggregate<span class="token punctuation">></span></span> <span class="token keyword">where</span> <span class="token class-name">TAggregate</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">DecideResult<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">,</span> TAggregate<span class="token punctuation">></span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">Handler<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">,</span> TAggregate<span class="token punctuation">></span></span> handle<span class="token punctuation">,</span> <span class="token class-name">TAggregate</span> aggregate<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token function">handle</span><span class="token punctuation">(</span>aggregate<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> updatedAggregate <span class="token operator">=</span> result<span class="token punctuation">.</span>NewState <span class="token operator">??</span> aggregate<span class="token punctuation">;</span> <span class="token keyword">return</span> DecideResult<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>updatedAggregate<span class="token punctuation">,</span> updatedAggregate<span class="token punctuation">.</span><span class="token function">DequeueUncommittedEvents</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>As you see, we’re not passing in tests, the command instance, but running the aggregate methods with input parameters.</strong> As our aggregate publish events through the internal pending events queue, we’re still checking events in.</p> <p>To be able to get events from the pending collection, we had to inject the <em>decide</em> method. We defined that <em>AggregateTestExtensions</em> and passed in spec definition. It’s again a bit of magic, but it’s straightforward if we look closely.</p> <p>We expect that as the first parameter, we’ll get a handler provided in the <em>When</em> method. As the second, we’ll get aggregate. We need to run the provided handler on aggregate to get the new aggregate state. Once we have it, we can dequeue events and return both.</p> <p>We must define a few extensions to <em>WhenDeciderSpecificationBuilder</em> to be able to pass the regular functions.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">HandlerSpecificationExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">When</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">WhenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>Handler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token keyword">when</span><span class="token punctuation">,</span> <span class="token keyword">params</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> whens <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">when</span><span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>whens<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>WhenMapping<span class="token operator">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token operator">></span><span class="token punctuation">.</span>ToHandler<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ThenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">When</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">WhenDeciderSpecificationBuilder<span class="token punctuation">&lt;</span>Handler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span><span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token keyword">when</span><span class="token punctuation">,</span> <span class="token keyword">params</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> whens <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">when</span><span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>whens<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>WhenMapping<span class="token operator">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token operator">></span><span class="token punctuation">.</span>ToHandler<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">WhenMapping<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Handler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">ToHandler</span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span></span> <span class="token keyword">when</span><span class="token punctuation">)</span> <span class="token operator">=></span> _ <span class="token operator">=></span> DecideResult<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">when</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Handler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span> <span class="token function">ToHandler</span><span class="token punctuation">(</span><span class="token class-name">Action<span class="token punctuation">&lt;</span>TState<span class="token punctuation">></span></span> <span class="token keyword">when</span><span class="token punctuation">)</span> <span class="token operator">=></span> state <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">when</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> DecideResult<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TState<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>This code snippet may look cryptic, but it just defines the permutation of handlers you can pass to the test method:</p> <ul> <li>returning state from the handler (for the newly created instance)</li> <li>running the handler on the current state and not returning anything.</li> </ul> <p><strong>Bang! We have also aggregates covered!</strong></p> <h2 id="state-based-aggregates-tests" style="position:relative;"><a href="#state-based-aggregates-tests" aria-label="state based aggregates tests permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>State-based Aggregates tests</h2> <p>Event-driven is great, but what if we’d like just to test aggregates that are not producing any events? Sure we can!</p> <p>Let’s say that we have the following version of the shopping cart:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> TotalPrice <span class="token operator">=></span> ProductItems<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>TotalPrice<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Open</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> cartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCart</span><span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId<span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> id<span class="token punctuation">;</span> ClientId <span class="token operator">=</span> clientId<span class="token punctuation">;</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddProduct</span><span class="token punctuation">(</span> <span class="token class-name">IProductPriceCalculator</span> productPriceCalculator<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Adding product for the cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> productPriceCalculator<span class="token punctuation">.</span><span class="token function">Calculate</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">MergeWith</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span> <span class="token class-name">PricedProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Removing product from the cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>UnitPrice</span><span class="token punctuation">}</span></span><span class="token string">' was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span>existingProductItem<span class="token punctuation">.</span><span class="token function">HasEnough</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">existingProductItem<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem<span class="token punctuation">.</span><span class="token function">HasTheSameQuantity</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">Subtract</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Confirming cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Canceling cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Canceled<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">PricedProductItem<span class="token punctuation">?</span></span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> ProductItems <span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span><span class="token function">MatchesProductAndPrice</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>No events here! What would the test look like?</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartTests</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Random</span> random <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">HandlerSpecification<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">,</span> ShoppingCart<span class="token punctuation">></span></span> Spec <span class="token operator">=</span> Specification<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">,</span> ShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">class</span> <span class="token class-name">DummyProductPriceCalculator</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IProductPriceCalculator</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name"><span class="token keyword">decimal</span></span> price<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">DummyProductPriceCalculator</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">decimal</span></span> price<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>price <span class="token operator">=</span> price<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">IReadOnlyList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> <span class="token function">Calculate</span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name">ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span></span> productItems<span class="token punctuation">)</span> <span class="token operator">=></span> productItems<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> PricedProductItem<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>pi<span class="token punctuation">,</span> price<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenNonExistingShoppingCart_WhenOpenWithValidParams_ThenSucceeds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> shoppingCartId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> ShoppingCart<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> state<span class="token punctuation">.</span>Id<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> state<span class="token punctuation">.</span>ClientId<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>clientId<span class="token punctuation">)</span><span class="token punctuation">;</span> state<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">BeEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> state<span class="token punctuation">.</span>Status<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span><span class="token punctuation">;</span> state<span class="token punctuation">.</span>TotalPrice<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">GivenOpenShoppingCart_WhenAddProductWithValidParams_ThenSucceeds</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> shoppingCartId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> <span class="token function">ValidProductItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> price <span class="token operator">=</span> random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> priceCalculator <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DummyProductPriceCalculator</span><span class="token punctuation">(</span>price<span class="token punctuation">)</span><span class="token punctuation">;</span> Spec<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token function">OpenedShoppingCart</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>cart <span class="token operator">=></span> cart<span class="token punctuation">.</span><span class="token function">AddProduct</span><span class="token punctuation">(</span>priceCalculator<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>state<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> state<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">NotBeEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> state<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>PricedProductItem<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>productItem<span class="token punctuation">,</span> price<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">OpenedShoppingCart</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> shoppingCartId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ShoppingCart<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItem</span> <span class="token function">ValidProductItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> ProductItem<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> Random<span class="token punctuation">.</span><span class="token function">Next</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Looks neat, but what changes do we need to add?</p> <p>Let’s start with updating the test run method in <em>WhenDeciderSpecificationBuilder</em> also to return state and assume that the events collection may be empty:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token return-type class-name">Lazy<span class="token punctuation">&lt;</span>TestResult<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">RunTest</span><span class="token punctuation">(</span><span class="token class-name">TCommand<span class="token punctuation">[</span><span class="token punctuation">]</span></span> commands<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> currentState <span class="token operator">=</span> <span class="token punctuation">(</span>getCurrentState <span class="token operator">??</span> decider<span class="token punctuation">.</span>GetInitialState<span class="token punctuation">)</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> resultEvents <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> command <span class="token keyword">in</span> commands<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>newEvents<span class="token punctuation">,</span> state<span class="token punctuation">)</span> <span class="token operator">=</span> decider<span class="token punctuation">.</span><span class="token function">Decide</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> currentState<span class="token punctuation">)</span><span class="token punctuation">;</span> resultEvents<span class="token punctuation">.</span><span class="token function">AddRange</span><span class="token punctuation">(</span>newEvents<span class="token punctuation">)</span><span class="token punctuation">;</span> currentState <span class="token operator">=</span> state <span class="token operator">??</span> newEvents<span class="token punctuation">.</span><span class="token function">Aggregate</span><span class="token punctuation">(</span>currentState<span class="token punctuation">,</span> decider<span class="token punctuation">.</span>Evolve<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TestResult<span class="token punctuation">&lt;</span>TState<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span><span class="token punctuation">(</span>currentState<span class="token punctuation">,</span> resultEvents<span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That allows us to take the state based on the <em>evolve</em> method and directly from <em>decide</em> function result.</p> <h2 id="summary" style="position:relative;"><a href="#summary" aria-label="summary permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Summary</h2> <p><strong>As you can see, we started with the <em>Decider</em> pattern and tried to use it in our event-sourced tests.</strong> Yet it appeared that the concept is flexible enough and strong that on top of it, we were able to test aggregates (event-driven and state-based).</p> <p>The code samples show the C# implementation, but using the following patterns can also be applied to any other programming language.</p> <p>Still, you don’t need to do it on your own, if you’re in .NET space, just <a href="https://www.nuget.org/packages/Ogooreck">add the NuGet</a>, and you’ll be able to test your business logic as described in this article.</p> <p>I hope that you like it!</p> <p>Read also:</p> <ul> <li><a href="/en/testing_event_driven_projections/">How to test event-driven projections</a></li> <li><a href="/en/writing_and_testing_business_logic_in_fsharp/">Writing and testing business logic in F#</a></li> <li><a href="/en/behaviour_driven_design_is_not_about_tests/">Behaviour-Driven Design is more than tests</a></li> <li><a href="/en/ogooreck_sneaky_bdd_testing_framework/">Ogooreck, a sneaky testing library in BDD style</a></li> <li><a href="/en/i_tested_on_production/">I tested it on production and I’m not ashamed of it</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Dive a bit deeper, look a bit wider]]>https://event-driven.io/en/dive_a_bit_deeper_look_a_bit_wider/https://event-driven.io/en/dive_a_bit_deeper_look_a_bit_wider/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0e1d7d3ffc8896925c2ff3871347fc68/8299d/2022-09-28-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAQFAgb/xAAVAQEBAAAAAAAAAAAAAAAAAAAAA//aAAwDAQACEAMQAAABXc5/VY0iWH//xAAaEAACAwEBAAAAAAAAAAAAAAAAAQMREgIU/9oACAEBAAEFAo+z0PLkd2aZpn//xAAWEQADAAAAAAAAAAAAAAAAAAAAARL/2gAIAQMBAT8Blks//8QAFxEAAwEAAAAAAAAAAAAAAAAAAAEREv/aAAgBAgEBPwGo0j//xAAWEAEBAQAAAAAAAAAAAAAAAAAAMSD/2gAIAQEABj8CVcf/xAAaEAACAgMAAAAAAAAAAAAAAAAAEQEQIUFR/9oACAEBAAE/IV7QzhWImYlR/9oADAMBAAIAAwAAABCv/wD/xAAXEQEBAQEAAAAAAAAAAAAAAAABADFh/9oACAEDAQE/EAMbvf/EABYRAQEBAAAAAAAAAAAAAAAAAAEQMf/aAAgBAgEBPxBbZf/EABkQAQEBAQEBAAAAAAAAAAAAAAERACExUf/aAAgBAQABPxCIPLWyKUJ8OZie8M46ADAzb3f/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/0e1d7d3ffc8896925c2ff3871347fc68/c60e9/2022-09-28-cover.jpg" srcset="/static/0e1d7d3ffc8896925c2ff3871347fc68/37402/2022-09-28-cover.jpg 200w, /static/0e1d7d3ffc8896925c2ff3871347fc68/4cda9/2022-09-28-cover.jpg 400w, /static/0e1d7d3ffc8896925c2ff3871347fc68/c60e9/2022-09-28-cover.jpg 800w, /static/0e1d7d3ffc8896925c2ff3871347fc68/6c738/2022-09-28-cover.jpg 1200w, /static/0e1d7d3ffc8896925c2ff3871347fc68/56dca/2022-09-28-cover.jpg 1600w, /static/0e1d7d3ffc8896925c2ff3871347fc68/8299d/2022-09-28-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>We live in a time of information overload. We are constantly stimulated. Our focus skills are similar to a goldfish.</strong> Seemingly our life is easier than it used to be. The same can be observed in programming. Our tools are getting more accessible, and processes are becoming more automated. Knowledge is also at your fingertips: online courses, blogs, and books. In fact, it’s hard to choose what and where to learn from. Many companies even <a href="/en/revolution_now/">employ DevAdvocate</a> to show you how to best use the tool and why it is worth it.</p> <p><strong>However, the technologies’ evolution does not want to stop.</strong> More and more, faster and faster. Javascript frameworks are getting so out of fashion every week.</p> <p>The dynamic development of technology, the seemingly good quality of training materials and, truth be told, laziness make our knowledge more and more superficial.</p> <p><strong>Some time ago, I wrote <a href="/en/structural_typing_in_type_script/">about TypeScript and how apparently it is similar to C# and Java, yet very different</a>.</strong> Because of that, Java and C# devs using TypeScripts tend to ignore differences and make cliches of their habits. Generics maze, base class on top of other base class.</p> <p><strong>In one of my projects, one colleague claimed that Postgres was not performing well for his use case. He stated that he will use Redis because of its in-memory cache.</strong> He got hooked on his idea. Proof of concept worked well, but it turned out (surprisingly…) that when the cache is invalidated, you have to rebuild it again. In addition, if you only keep data in memory, it may disappear when Time To Leave is reached. Once he realised those surprising side effects, he asked the operations team if they could make backups of the in-memory Redis cache. He didn’t think that this was an unusual request. The ops team had a contrary opinion. That’s one I heard for the first time about my colleague’s brilliant concept. Out of curiosity, I looked at the Postgres setup to see why it’s underperforming. Of course, it turned out that there wasn’t a single index defined in the database (except for the primary keys). Curtain.</p> <p><strong>One of the things I like to follow when I perform recruitment is the N+1 problem and ways to optimise ORM.</strong> Not that it is a thrilling topic for me, but it allows me to learn a lot about candidates and the industry. As I mentioned above, knowledge of the basics of databases is rare - this is the world we’re living. The majority uses ORM, but are they doing it consciously? Unfortunately, it turns out that things such as solving the N+1 problem or disabling tracking changes are also secret knowledge.</p> <p><strong>I hear once or twice in a discussion that there is no problem keeping half a million records in the event stream in Event Sourcing.</strong> In Event Sourcing, the entity’s current state is rebuilt from all events in the stream each time we want to perform the business operation. Also, I don’t know any pattern (besides Big Data) where getting half a million records is the intended use case. In Event Sourcing, the streams must be as short-lived as possible and, therefore, the shortest. I wrote about it in my article <a href="/en/how_to_do_event_versioning/">How to (not) do the events versioning?</a> and explained in <a href="https://www.architecture-weekly.com/p/webinar-2-keep-your-streams-short">Keep your streams short! Or how to model Event-Sourced systems efficiently</a> webinar. Some may say that we can use Snapshots, but then we’re getting to the brilliant idea with the Redis cache I explained above.</p> <p><strong>A similar thing can be told about the CQRS pattern.</strong> Greg Young <a href="https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf">wrote a relatively short document explaining it</a>. Yet, for some reason, people took some pictures out of context, e.g. that one about multiple databases, eventual consistency and event sourcing. CQRS doesn’t need all of that; if someone checked in Greg’s explanation, then for sure, they would notice that it’s shown as <strong>one of the options</strong>, not the recommended one. They would also read that CQRS is about segregating behaviours, so slicing the architecture by the business operations. Yet, people prefer to read hottakes on blogs or conferences instead of reading original works. That’s also one of the reasons why I tried to explain it on <a href="/en/cqrs_facts_and_myths_explained/">my blog article</a> and <a href="https://www.architecture-weekly.com/p/webinar-4-from-cqrs-to-crud-in-practice">webinar</a>, maybe that will reach some people.</p> <p><strong><a href="https://martendb.io/">Marten</a> users ask quite often <em>“How to make joins between documents?”</em>.</strong> And the answer is _“You should not”. As I described in <a href="/en/key-value-stores/">Unobvious things you need to know about key-value stores</a>, each database type has its specifics. Relational databases are denormalised, and document ones are normalised. They work best if we’re querying for a specific document. So we should rather keep nested data than join it with other document types.</p> <p><strong>Examples could be multiplied, but complaining won’t help</strong> I once heard that if we want to be at least a decent programmer, we should look at least one level lower than the one we work daily. So if we use ORM, apart from understanding it, let’s also understand the basics of relational databases. If we are doing a frontend, let’s understand the rules of building a sound WebApi and how the backend roughly works.</p> <p>If we use a document database or event sourcing, let’s look for their specific modelling rules. If we use a new language, let’s try to understand its conventions.</p> <p><strong>We should strive to understand our tool and try to <a href="/en/event_streaming_is_not_event_sourcing/">break through the marketing sold by their authors</a>.</strong> Let’s not base our design on gut feeling and question hype and what we read on the blog.</p> <p><strong>Let’s not be lazy and try to look a bit deeper. Even one level deeper than usual. And look a bit wider.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[On the importance of setting boundaries in team management]]>https://event-driven.io/en/on_the_importance_of_shaping_the_boundaries_in_team_management/https://event-driven.io/en/on_the_importance_of_shaping_the_boundaries_in_team_management/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b8b49eac6f7f53d2f2d7e3227f5d1926/8299d/2022-09-21-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAUBAgME/8QAFgEBAQEAAAAAAAAAAAAAAAAAAQID/9oADAMBAAIQAxAAAAHaOKmdPBYD/8QAHBAAAgICAwAAAAAAAAAAAAAAAQIAAwQSExQi/9oACAEBAAEFAnyQopzFK9muOF4qFXTzP//EABYRAQEBAAAAAAAAAAAAAAAAAAABEf/aAAgBAwEBPwFkf//EABYRAQEBAAAAAAAAAAAAAAAAAAASIf/aAAgBAgEBPwGsW//EABwQAAICAgMAAAAAAAAAAAAAAAABAiERMRIyQf/aAAgBAQAGPwJySyPntGyVeFxzZ0R//8QAGhAAAwEBAQEAAAAAAAAAAAAAAAERITFh0f/aAAgBAQABPyFhCVnopn2xpdFH5JJSL0mXYp4H/9oADAMBAAIAAwAAABDg7//EABcRAQADAAAAAAAAAAAAAAAAAAEQESH/2gAIAQMBAT8QTai//8QAFhEBAQEAAAAAAAAAAAAAAAAAARAh/9oACAECAQE/EAaj/8QAGxABAAMBAAMAAAAAAAAAAAAAAQARIVFBkcH/2gAIAQEAAT8Qr9A2KF43mxzmBCj85G2v7QRNCRFyCdOUrHAyMUp31Z//2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b8b49eac6f7f53d2f2d7e3227f5d1926/c60e9/2022-09-21-cover.jpg" srcset="/static/b8b49eac6f7f53d2f2d7e3227f5d1926/37402/2022-09-21-cover.jpg 200w, /static/b8b49eac6f7f53d2f2d7e3227f5d1926/4cda9/2022-09-21-cover.jpg 400w, /static/b8b49eac6f7f53d2f2d7e3227f5d1926/c60e9/2022-09-21-cover.jpg 800w, /static/b8b49eac6f7f53d2f2d7e3227f5d1926/6c738/2022-09-21-cover.jpg 1200w, /static/b8b49eac6f7f53d2f2d7e3227f5d1926/56dca/2022-09-21-cover.jpg 1600w, /static/b8b49eac6f7f53d2f2d7e3227f5d1926/8299d/2022-09-21-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Paweł Janas is a significant figure in Polish football history. He was a decent defender on his own, then coaching our best clubs and heading the Polish national team. <strong>His coaching maxim was: <em>“I don’t fuck around”</em>.</strong> Yes, Paweł Janas is a specific person, a glass of whiskey in one hand, a cigar in the other, old school.</p> <p><strong>For the last several years of my work in the industry, I have worked with people. Most of the time, as a leader.</strong> I quickly became a leader (after about a year of experience). I don’t think it was the wisest move by my boss. Yet, that allowed me to learn to manage in the wild. Working with people, especially managing them, is challenging in many ways. There is always something going on; constantly juggling emotions, problems and solutions.</p> <p><strong>I started like most of us: trying to play the role model. I hoped that team would follow me if I did well and set an example.</strong> I thought people would appreciate the commitment. It turned out that some enjoyed it, and some treated it as an opportunity to abuse my goodwill. Everyone is different, and each person requires a different motivation. You can’t treat everyone the same way, and you can’t make everyone happy. One of my employees told me that <em>“Oskar, I know that I wasn’t doing my best recently. It’s good that you straighten me. I need someone to wig me.”</em>. I am not a person who raises his voice or swears too often, especially at someone else. Yet, having to “straighten conversation” is very stressful. Nevertheless, to be an effective leader, I also had to learn to give negative feedback. How to conduct it?</p> <p><strong>The most important is to focus on the consequences of what happened, not the person.</strong> Explain to the person why it was wrong. It isn’t easy because often, people don’t even realize that something has gone wrong. Moreover, such a conversation is not about venting your frustration. The point is to explain what happened, understand what it looked like from the employee’s perspective, and determine what caused the error. If we are annoyed, attacking straight away, the conversation won’t go well. We will put the person in a defensive position. They will close, and will either argue with us or, worse, nod silently. Why is it worse? It means they didn’t understand what we meant, so they will not conclude. Then give particular points of what you expect in the future in a similar situation. The more discussion is focused on the specific topic instead of the personal discussions, the better.</p> <p><strong>Generally, if we are already in disaster mode, it’s too late anyway.</strong> Nerves won’t help. The mistake was made earlier, and the process failed. It is best to prevent issues instead of fixing them in a rush. I believe that being a leader means being available for the team and feeling the pulse. As I wrote in the <a href="/en/architect_manifesto/">Architecture Manifesto</a>, technical leaders should code. They should also do the dirty work when the team needs help. That also means being a shield for the team for external issues. Of course, not entirely. If you take everything on yourself, you’re on the highway to burnout. You should protect the team but also teach them responsibility and what you expect from them. Transparency is key.</p> <p><strong>Here we get back to Paweł Janas and his <em>“not fucking around”</em>. In my opinion, the key to working in a team is to define clear boundaries.</strong> They should make it clear what’s expected from the team, what behaviour we allow and what not. Consequences should be equally clear.</p> <p><strong>It’s crucial to only set the rules that we can verify.</strong> If we are not able to do that, then you can be sure that people will learn to go around them. What’s more people will then project this to other, more important rules. So don’t waste time and effort on the rules you cannot enforce. People will have as much respect for us as for mindlessly placed road signs.To build authority, you don’t need to build full codex, but the most important rules around the process.</p> <p>Okay, but I’m talking about enforcement, and this may sound like a contradiction of Janas’ maxim. Still, that goes pretty well together. <strong>If we define a boundaries, they will give people a sense of security.</strong> How come? If people know what the requirements are and why they are set, they know how to behave. They know that as long as they stay within the given boundaries, they can do what they think is right. We don’t have to do micro-management; we give people the opportunity to have authority and to make safe mistakes. If people don’t know what we’re expecting from them, they don’t know what’s wrong. This, in turn, may paralyze their initiative. Especially if our enforcement is then illogical and unclear.</p> <p>Programmers are stubborn and picky; if we push them too hard, they will run away elsewhere. <strong>We must find a good balance and give them a chance to prove themselves and develop.</strong></p> <p>The teacher from my daughter’s nursery told me that <em>“we always have to maneuver Ula in such a way that she thinks that’s her idea, otherwise she will not agree to something”</em>. Paradoxically, this is probably also a good recipe for managing programmers and in line with the Paweł Janas’ maxim.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[It doesn't have to be toxic at work]]>https://event-driven.io/en/it_doesnt_have_to_be_toxic_at_work/https://event-driven.io/en/it_doesnt_have_to_be_toxic_at_work/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f431906d892322f9badd1c3b88a31fb8/8299d/2022-09-14-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMFBP/EABQBAQAAAAAAAAAAAAAAAAAAAAH/2gAMAwEAAhADEAAAAULzLGsRRP/EABoQAQADAAMAAAAAAAAAAAAAAAEAAhEDIjL/2gAIAQEAAQUCq4CzlsDbzvWf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAHhAAAgAGAwAAAAAAAAAAAAAAAAECAxARIlESIWH/2gAIAQEABj8CxmiiTy3cXDXYvS1P/8QAHBAAAwACAwEAAAAAAAAAAAAAAAERITFBUWGB/9oACAEBAAE/IWaTyiNnLLVRZy1GC5Gkex8Qq1s//9oADAMBAAIAAwAAABBr3//EABURAQEAAAAAAAAAAAAAAAAAAAEQ/9oACAEDAQE/EBn/xAAVEQEBAAAAAAAAAAAAAAAAAAAQEf/aAAgBAgEBPxCH/8QAHRABAQACAgMBAAAAAAAAAAAAAREAITFBUXGB8P/aAAgBAQABPxBUBHUG/uKAJrfZdOuMdnYtbbeTxM14CrZvTltwgPfL++YqK3n/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/f431906d892322f9badd1c3b88a31fb8/c60e9/2022-09-14-cover.jpg" srcset="/static/f431906d892322f9badd1c3b88a31fb8/37402/2022-09-14-cover.jpg 200w, /static/f431906d892322f9badd1c3b88a31fb8/4cda9/2022-09-14-cover.jpg 400w, /static/f431906d892322f9badd1c3b88a31fb8/c60e9/2022-09-14-cover.jpg 800w, /static/f431906d892322f9badd1c3b88a31fb8/6c738/2022-09-14-cover.jpg 1200w, /static/f431906d892322f9badd1c3b88a31fb8/56dca/2022-09-14-cover.jpg 1600w, /static/f431906d892322f9badd1c3b88a31fb8/8299d/2022-09-14-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>We work in an industry that’s full of passionate people. Doing what you like and getting paid for it is a privilege and a curse.</strong> We’re vulnerable to workaholism and its harmful effects. One of them may be staying too long in a place where we shouldn’t be. I have had several such projects in my life. I was doing overtime because I felt responsible: for the project, for the team, and probably also for my pride. Sometimes I didn’t log all the hours into the timesheet because I thought that the company was doing me a favour, that I had a project where I did exciting things. I was learning a lot, doing stuff I liked but burning out slowly.</p> <p><strong>Of course, it ended with moments of extreme psychophysical exhaustion.</strong> Worse, you can only find out that you’re in such a state when you break the vicious circle. Getting the proper perspective is hard if you’re in it, especially if you stayed in it for too long. It becomes our norm when we are in a cesspool, and everything is dirty. Unfortunately, the body cannot deceive itself. It is not only about physical health but, above all, mental health. Being in a state of permanent stress, fatigue, and toxic project relationships does not leave us indifferent. Worse, it’s not like you change the job, wash your hands, and it magically starts to be okay. Some of these changes are irreversible. A mental injury requires the same recovery as a physical injury. If we break a leg, we must take care of it and adequately undergo rehabilitation. We may no longer play our favourite sport if we break it several times. Best not to break at all.</p> <p><strong>Many times I wasn’t great at caring about my mental safety. It is dangerous if you’re an employee but even worse if you’re a team leader.</strong> Fortunately, I had enough oil in my head that I never demanded overtime or even passion from employees. Of course, I tried to motivate them to grow but not to put additional pressure. Of course, I certainly didn’t always succeed. As a father of a 3 years old child, I see many similarities between raising a kid and handling a team. People in your group, like your child, will react like a sponge. They will absorb all your practices and the atmosphere you created. Both good and bad behaviour. What’s worse, the more powerful and impactful the unconscious message and vibes you send between words. You may not say anything, but the message is sent. If the leader replies at any strange time, sends e-mails at weird times or talks on Slack, even if it does not require writing back, he subconsciously lets you know that this is the norm. It shouldn’t be.</p> <p><strong>I have an unpopular opinion that I am glad that there will be more people from boot camps and, therefore, less passion for our profession.</strong> Hopefully, this will introduce a more rational approach. I hope there will be less <em>“because I had to”</em>, a blind race for self-development, which is often used by employers for their benefits. Even during job interviews or one-to-ones, emotional blackmail sometimes occurs that <em>“well, we will not give money, but we will give a chance to learn it”</em>. It would be much healthier if both sides treated it as a business. So, for instance: <em>“I offer such wage for fulfilling such requirements, I expect following effort”</em>. We either agree or not. That’s also why I don’t like the recent fashion for equity, compensation, etc. This just blurry the agreement and expectations. Usually for the employer’s benefit.</p> <p><strong>If you’re in a toxic environment, leave it.</strong> Of course, try to change it first and ensure the issue is not in you. But if you don’t see improvement and are still feeling bad, just leave. Don’t call yourself a quitter. It’s self-defence. Staying in a toxic environment changes you. Sometimes those changes are irreversible. You may not notice that, but your close ones will.</p> <p><strong>A healthy work environment is not based on fruit delivery on Thursday and beer on Friday. It is not a curved screen for half a room.</strong> This is a place where we feel safe and know what is expected of us and that these expectations are rational. A place where there is less passion but explicit contract and fulfilment of them. The employer should organize work so that the employee can focus on it during working hours, including the required development and learning. The employee should be encouraged to take their time after work to recharge. The employer should not require this. On the other hand, the employees should focus on doing what they agreed and do that effectively during working hours.</p> <p><strong>And above all. There is a payment for the work getting done. No hidden emotional blackmailing.</strong></p> <p>That’s a transparent deal and, I think, a healthier one.</p> <p>Read also:</p> <ul> <li><a href="/en/a_few_words_about_workaholism/">Don’t be like Ebenezer Scrooge. A few words about workaholism</a></li> <li><a href="/en/keeping_overachieving_freak_on_a_leash">Keeping our overachieving freak on a leash</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If you liked this article, check also <a href="/en/2022-04-13--agile_vs_introverts">Agile vs Introverts</a> and <a href="/en/what_does_a_construction_failure_have_to_do_with_our_authorities">What does a construction failure have to do with our authorities?</a>.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Event Versioning with Marten]]>https://event-driven.io/en/event_versioning_with_marten/https://event-driven.io/en/event_versioning_with_marten/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b871002a0dad67e44f57319ae70c18ef/8299d/2022-09-07-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBP/EABYBAQEBAAAAAAAAAAAAAAAAAAEAAv/aAAwDAQACEAMQAAABg7NhylyP/8QAGhAAAgIDAAAAAAAAAAAAAAAAAQIAESExMv/aAAgBAQABBQIXOgREXAWouv/EABURAQEAAAAAAAAAAAAAAAAAAAAR/9oACAEDAQE/Aar/xAAVEQEBAAAAAAAAAAAAAAAAAAAAIf/aAAgBAgEBPwFH/8QAGRABAAMBAQAAAAAAAAAAAAAAAAERMSFB/9oACAEBAAY/ApbkW9U123//xAAbEAEAAgMBAQAAAAAAAAAAAAABABEhMUGBkf/aAAgBAQABPyEgs01jErofoiXqOpyaAsjKux7P/9oADAMBAAIAAwAAABDXD//EABcRAAMBAAAAAAAAAAAAAAAAAAABESH/2gAIAQMBAT8QSTSj/8QAFxEBAQEBAAAAAAAAAAAAAAAAAQBxgf/aAAgBAgEBPxAHlq//xAAbEAEBAAIDAQAAAAAAAAAAAAABEQAhQVFhsf/aAAgBAQABPxAEobuT1izgNJAVyE6+YupeOEVcvYe4OqXMhNYK2gweuf/Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b871002a0dad67e44f57319ae70c18ef/c60e9/2022-09-07-cover.jpg" srcset="/static/b871002a0dad67e44f57319ae70c18ef/37402/2022-09-07-cover.jpg 200w, /static/b871002a0dad67e44f57319ae70c18ef/4cda9/2022-09-07-cover.jpg 400w, /static/b871002a0dad67e44f57319ae70c18ef/c60e9/2022-09-07-cover.jpg 800w, /static/b871002a0dad67e44f57319ae70c18ef/6c738/2022-09-07-cover.jpg 1200w, /static/b871002a0dad67e44f57319ae70c18ef/56dca/2022-09-07-cover.jpg 1600w, /static/b871002a0dad67e44f57319ae70c18ef/8299d/2022-09-07-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>War never changes; migrations are always complex. It’s always a mind gymnastics and searching <a href="/en/the_risk_of_ignoring_risks">for risks</a> and what may go wrong. Sometimes we try to cheat, ignore the consequences, and take the fastest. That usually ends up with breaking things. Breaking our data is one of the worst consequences of our YOLO attitude. We all have scary tales about our mishaps, yet we still believe that “just doing a simple SQL” is manageable. We can split people into two groups: those that are doing backups and those that will do them.</p> <p>In Event Sourcing, we have a log of business events registered in our system. This log and events are immutable. What has been seen cannot be unseen. Of course, if you read 1984 or watched “Back to the Future”, you know that the past can be changed. Yet, it doesn’t mean that it should. I wrote about that in the past, explaining that:</p> <ul> <li><a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">The best rule is not to break changes, as it increases predictability</a>.</li> <li><a href="/en/what_texting_ex_has_to_do_with_event_driven_design/">the only sure way to correct the past is to compensate our operations</a>, for instance, by appending correcting events. We live in a distributed world; running SQL migration in a single module won’t propagate those changes to other modules.</li> <li><a href="/en/how_to_do_event_versioning/">we should try to avoid migrations and support multiple events versions by working on our streams design</a>,</li> </ul> <p>Yet, sometimes we must act pragmatically and apply the above rules to support multiple event schemas. I described some of them in <a href="/en/how_to_do_event_versioning/">Simple patterns for events schema versioning</a>. Recently, I added Upcasting strategy in Marten (versions <a href="https://github.com/JasperFx/marten/releases/tag/5.9.0">5.9.0</a> and <a href="https://github.com/JasperFx/marten/releases/tag/5.10.0">5.10.0</a>.</p> <p>Upcasting is a process of transforming the old JSON schema into the new one. It’s performed on the fly each time the event is read. You can think of it as a pluggable middleware between the deserialisation and application logic. Having that, we can either grab raw JSON or a deserialised object of the old CLR type and transform them into the new schema. Thanks to that, we can keep only the last version of the event schema in our stream aggregation or projection handling. Check the <a href="https://martendb.io/events/versioning.html#upcasting-advanced-payload-transformationsl">documentation</a>, as I tried to make it a comprehensive source, not only the API description.</p> <p>In this post, I’ll focus on explaining how to support multiple schema versions.</p> <p>Let’s say that we started with the following event representing information that the client added the product to the shopping cart:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ProductId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Quantity <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It has information on which product we added (<em>ProductId</em>) and how many items we selected (<em>Quantity</em>). It worked well for some time, but we realised that product prices are changing. Surprise! We would not want our clients to pay more but <em>freeze</em> the price when they decide to add a product. To do that we decided to also store information about the price. By having that, even if the price changes, we’ll know precisely what the price client has selected. Ah, and we’d like to also rename <em>ShoppingCartId</em> to <em>CartId</em> as we think the prefix is redundant. The updated event will look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ProductId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Quantity<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span><span class="token punctuation">?</span></span> Price <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You may notice that we’re making the price nullable. If we could guess the price, then we could make it not null and provide some default value, e.g. <em>666</em>. Yet that’s not always possible. In this case, also. To be accurate, even in the traditional approach, if we weren’t versioning our prices per time, then SQL migration wouldn’t help. The issue with that is that this code will allow creating the invalid event from the code. If we’d like to have the compiler checking that, we could do something like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProductItemAddedToShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ShoppingCartId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ProductId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Quantity <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span><span class="token punctuation">?</span></span> Price <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> productId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> quantity<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span><span class="token punctuation">?</span></span> price <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> ShoppingCartId <span class="token operator">=</span> shoppingCartId<span class="token punctuation">;</span> ProductId <span class="token operator">=</span> productId<span class="token punctuation">,</span> Quantity <span class="token operator">=</span> quantity<span class="token punctuation">,</span> Price <span class="token operator">=</span> price<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItemAddedToShoppingCart</span> <span class="token function">For</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> productId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> quantity<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> price <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> productId<span class="token punctuation">,</span> quantity<span class="token punctuation">,</span> price<span class="token punctuation">)</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re making our constructor private, it’ll be only used during deserialisation, and our code will use the static factory method. By that, we won’t allow creating new events without price. Yet, we still need to support the old one.</p> <p>Let’s also say that we’re pedantic and like Object Oriented Programming. We realised that it excessively hurts our eyes to see the flattened structure. We’d like to group product-related items into a nested object. After that, our third schema version will look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">PricedProductItem</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProductId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Quantity<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span><span class="token punctuation">?</span></span> Price <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Now, for obvious reasons, the serialiser (for apparent reasons) won’t automatically know how to map it to the new schemas.</p> <p>Before we show how to solve that, let’s stop and discuss the <strong>naming of the events representing different schemas</strong>. We can use:</p> <ul> <li>the same name,</li> <li>different namespace,</li> <li>different name with a version number, e.g. <em>ProductItemAddedToShoppingCartV1</em>, <em>ProductItemAddedToShoppingCartV2</em>, <em>ProductItemAddedToShoppingCartV3</em>,</li> <li>different name with meaningful suffix, e.g <em>ProductItemAddedWithPriceToShoppingCart</em> or <em>ProductItemAddedToShoppingCartWithGroupedInformations</em>.</li> </ul> <p>I usually lean toward explicit naming, as it’s more precise and shows us that those events are different. Because they’re different. We, humans, can correlate things by abstract thinking. Computers can’t, serialisers can’t. Yet, even if we see the same name, or just the <em>V27</em> suffix may get lost. The choice is yours. Marten supports all of the options.</p> <p>There are two main ways of upcasting the old schema into the new one:</p> <ul> <li><strong>CLR types transformation</strong> - if we’re okay with keeping the old CLR class in the codebase, we could define a function that takes the instance of the old type and returns the new one. Internally it will use default deserialisation and event type mapping for the old CLR type and calls the upcasting function.</li> <li><strong>Raw JSON transformation</strong> - if we don’t want to keep the old CLR class or want to get the best performance by reducing the number of allocations, we can do raw JSON transformations. Most of the serialisers have classes enabling that. <a href="https://www.newtonsoft.com/json/help/html/queryinglinqtojson.htm">Newtonsoft Json.NET has JObject</a> and <a href="https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-use-dom-utf8jsonreader-utf8jsonwriter#use-jsondocument">System.Text.Json has JsonDocument</a>. This gives the best flexibility, but logic may be more cryptic and <em>stringly-typed</em>.</li> </ul> <p>Moreover, Marten allows registering those transformations using raw functions and dedicated classes.</p> <p>I’ll use those ways interchangeably, as you can also do that, to show how to version events with different naming strategies.</p> <h2 id="different-namespace-and-the-same-event-name-with-version-suffix" style="position:relative;"><a href="#different-namespace-and-the-same-event-name-with-version-suffix" aria-label="different namespace and the same event name with version suffix permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Different namespace and the same event name with version suffix</h2> <p>I’ll use CLR types transformations and simple functions registration. The Marten registration will look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StoreOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Events <span class="token comment">// event type name: "product_item_added_to_shopping_cart"</span> <span class="token punctuation">.</span><span class="token function">Upcast</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">V1<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V3<span class="token punctuation">.</span>ProductItem</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token comment">// event type name: "product_item_added_to_shopping_cart_v2"</span> <span class="token punctuation">.</span><span class="token function">Upcast</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token class-name">V2<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V3<span class="token punctuation">.</span>ProductItem</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Quantity<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Price<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token comment">// event type name: "product_item_added_to_shopping_cart_v3"</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">MapEventTypeWithSchemaVersion</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> documentStore <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DocumentStore</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>As we’re using the CLR transformation and keeping old event types, we need to use different namespaces for our events. The following definition assumes that the first version wasn’t appended with a suffix (e.g. we forgot to add it at the beginning), and then events with version two are appended with the <em>v2</em> suffix. We’d like the third version to be added with <em>v3</em> suffix. By that, we know what to expect and can define a specific mapping for each schema version. As you noticed, Marten allows you to pass specific version numbers explicitly and does internal mapping for you. FYI: Marten, by default, maps the CamelCase CLR class name into the lowered snake_case. For instance, the mapped event type name for the <em>ProductItemAddedToShoppingCart</em> will be “product_item_added_to_shopping_cart”.</p> <h2 id="the-same-event-name-with-version-suffix" style="position:relative;"><a href="#the-same-event-name-with-version-suffix" aria-label="the same event name with version suffix permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>The same event name with version suffix</h2> <p>As mentioned above, if we’d like to eliminate the old event types or we hate adding redundant allocations, we could use raw JSON transformations. I’ll use System.Text.Json as an example, but you can achieve the same with Newtonsoft Json.NET.</p> <p>Let’s now take transformations to the dedicated class. They’re just pure functions taking the raw <em>JsonDocument</em> and returning the mapped event. This will make it easier to do unit tests.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ProductItemAddedToShoppingCartUpcasters</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span> <span class="token function">V1</span><span class="token punctuation">(</span><span class="token class-name">JsonDocument</span> oldEventJson<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> oldEvent <span class="token operator">=</span> oldEventJson<span class="token punctuation">.</span>RootElement<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ShoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItem</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ProductId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"Quantity"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetInt32</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span> <span class="token function">V2</span><span class="token punctuation">(</span><span class="token class-name">JsonDocument</span> oldEventJson<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> oldEvent <span class="token operator">=</span> oldEventJson<span class="token punctuation">.</span>RootElement<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ShoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItem</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ProductId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"Quantity"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetInt32</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"Price"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetDecimal</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The registration will look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">Marten<span class="token punctuation">.</span>Services<span class="token punctuation">.</span>Json<span class="token punctuation">.</span>Transformations<span class="token punctuation">.</span>SystemTextJson<span class="token punctuation">.</span>JsonTransformations</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">ProductItemAddedToShoppingCartUpcasters</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StoreOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">UseDefaultSerialization</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">serializerType</span><span class="token punctuation">:</span>SerializerType<span class="token punctuation">.</span>SystemTextJson<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span>Events <span class="token comment">// event type name: "product_item_added_to_shopping_cart"</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Upcast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token function">Upcast</span><span class="token punctuation">(</span>V1<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// event type name: "product_item_added_to_shopping_cart_v2"</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Upcast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token function">Upcast</span><span class="token punctuation">(</span>V2<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// event type name: "product_item_added_to_shopping_cart_v3"</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">MapEventTypeWithSchemaVersion</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> documentStore <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DocumentStore</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You could also put the upcasting options configuration into the <em>ProductItemAddedToShoppingCartUpcasters</em> class to encapsulate all upcasting definitions:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ProductItemAddedToShoppingCartUpcasters</span> <span class="token punctuation">{</span> <span class="token comment">// (...) Upcasting methods</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">StoreOptions</span> <span class="token function">UpcastProductItemAddedToShoppingCartUpcasters</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">StoreOptions</span> options<span class="token punctuation">)</span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Events <span class="token comment">// event type name: "product_item_added_to_shopping_cart"</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Upcast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token function">Upcast</span><span class="token punctuation">(</span>V1<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// event type name: "product_item_added_to_shopping_cart_v2"</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Upcast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> <span class="token function">Upcast</span><span class="token punctuation">(</span>V2<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// event type name: "product_item_added_to_shopping_cart_v3"</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">MapEventTypeWithSchemaVersion</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> options<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then registration will look as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">ProductItemAddedToShoppingCartUpcasters</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> options <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StoreOptions</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">UseDefaultSerialization</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">serializerType</span><span class="token punctuation">:</span>SerializerType<span class="token punctuation">.</span>SystemTextJson<span class="token punctuation">)</span><span class="token punctuation">;</span> options<span class="token punctuation">.</span><span class="token function">UpcastProductItemAddedToShoppingCartUpcasters</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> documentStore <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DocumentStore</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3 id="different-event-names-with-the-explicit-suffix" style="position:relative;"><a href="#different-event-names-with-the-explicit-suffix" aria-label="different event names with the explicit suffix permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Different event names with the explicit suffix</h3> <p>If you’re a classy person, then you could also define upcasters like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProductItemAddedToShoppingCartV1toV3Upcaster</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">EventUpcaster<span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCart<span class="token punctuation">,</span> ProductItemAddedToShoppingCartWithPriceAndGroupedProduct<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name">ProductItemAddedToShoppingCartWithPriceAndGroupedProduct</span> <span class="token function">Upcast</span><span class="token punctuation">(</span> <span class="token class-name">ProductItemAddedToShoppingCart</span> @<span class="token keyword">event</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAddedToShoppingCartWithPriceAndGroupedProduct</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PricedProductItem</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProductItemAddedToShoppingCartV2toV3Upcaster</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">EventUpcaster<span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCartWithPrice<span class="token punctuation">,</span> ProductItemAddedToShoppingCartWithPriceAndGroupedProduct<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name">ProductItemAddedToShoppingCartWithPriceAndGroupedProduct</span> <span class="token function">Upcast</span><span class="token punctuation">(</span> <span class="token class-name">ProductItemAddedToShoppingCartWithPrice</span> @<span class="token keyword">event</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAddedToShoppingCartWithPriceAndGroupedProduct</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PricedProductItem</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Quantity<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Price<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ProductItemAddedToShoppingCartUpcasters</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">StoreOptions</span> <span class="token function">UpcastProductItemAddedToShoppingCartUpcasters</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">StoreOptions</span> options<span class="token punctuation">)</span> <span class="token punctuation">{</span> options<span class="token punctuation">.</span>Events <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Upcast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCartV1toV3Upcaster<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Upcast</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCartV2toV3Upcaster<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">MapEventTypeWithSchemaVersion</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCartWithPriceAndGroupedProduct<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> options<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>For such registration, you can don’t need to specify event type, as Marten will automatically use the old CLR event type name and perform mapping by convention:</p> <ul> <li><em>ProductItemAddedToShoppingCart</em> => “product_item_added_to_shopping_cart”,</li> <li><em>ProductItemAddedToShoppingCartWithPrice</em> => “product_item_added_to_shopping_cart_with_price”,</li> <li><em>ProductItemAddedToShoppingCartWithPriceAndGroupedProduct</em> => “product_item_added_to_shopping_cart_with_price_and_grouped_product”.</li> </ul> <p>Of course, you can override the event type name, e.g.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">Marten<span class="token punctuation">.</span>Events<span class="token punctuation">.</span>EventMappingExtensions</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProductItemAddedToShoppingCartV2toV3Upcaster</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">EventUpcaster<span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCartWithPrice<span class="token punctuation">,</span> ProductItemAddedToShoppingCartWithPriceAndGroupedProduct<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> EventTypeName <span class="token operator">=></span> <span class="token generic-method"><span class="token function">GetEventTypeNameWithSchemaVersion</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>V1<span class="token punctuation">.</span>ShoppingCartOpened<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name">ProductItemAddedToShoppingCartWithPriceAndGroupedProduct</span> <span class="token function">Upcast</span><span class="token punctuation">(</span> <span class="token class-name">ProductItemAddedToShoppingCartWithPrice</span> @<span class="token keyword">event</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAddedToShoppingCartWithPriceAndGroupedProduct</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PricedProductItem</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Quantity<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Price<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Marten provides a set of helper methods for a convention-based approach, but you can also explicitly define the event type name.</p> <h2 id="grand-finalle" style="position:relative;"><a href="#grand-finalle" aria-label="grand finalle permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Grand finalle</h2> <p>You may keep your stream aggregation and projection logic clean thanks to the upcasting. You may just use the last schema version, for instance:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">namespace</span> <span class="token namespace">WithTheSameName</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> ProductItems<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartStatus</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Opened<span class="token punctuation">,</span> <span class="token class-name">DateTime<span class="token punctuation">?</span></span> OpenedAt <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCart</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Client<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Status<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>OpenedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">V3<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> ProductItems<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>x <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">{</span> ProductId <span class="token operator">=</span> x<span class="token punctuation">.</span>Key<span class="token punctuation">,</span> Quantity <span class="token operator">=</span> x<span class="token punctuation">.</span>Value <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span><span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>Quantity <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">GroupBy</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToDictionary</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>Key<span class="token punctuation">,</span> x <span class="token operator">=></span> x<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>k <span class="token operator">=></span> k<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>When we need to introduce the new schema, we can do it with backward compatibility and support both old and new schema during the next deployment. Based on our business process lifetime, we can define the graceful period. For instance, helpdesk tickets live typically for 1-3 days. We can assume that active tickets will be using only the new event schema after two weeks from deployment. Of course, we should verify that, and events with the old schema will still be in the database. Yet, we can archive the inactive tickets, as they won’t be needed for operational purposes (they will be either warm or cold data). By doing that, we can make the old event schema obsolete and don’t need to maintain it.</p> <p>Is Upcasting a killer feature? Probably not. When used wisely, it can be a decent, pragmatic choice helping us in the migration strategies and being our safety belt, allowing us to evolve our model. It should be used with care; thus, this article.</p> <p><strong>Watch also more in the webinar:</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAAAAAAD6AF+hNEZAAABbUlEQVQoz4WS3W4TMRCF/dqgvgGXcM1LcIOQoKpIN222m+xmf/0zc2bGacljoN1NqFQhOFdj2eez54ydzhJV+asgWIv5gAqAl1/n+7tvnz9+uHn33gGXbb5W4LkEQEQgUXkDhlk+Pb+cz2dHDFUl5hQ8JYoxiZ0guvoPfpc4/fEvdADMzETkMHvVBNUQ98cdp1qOW429aLZsZVOEGPTaVVYxy2KndelCUIiYcOm1OP7s/WZT3Lb1NiuO1bG+78GX5w7DcPDU+Ya7Hc2dsYtJzYyYnzX3sSm7otrdteWPafOlejwQpTUKAPMFMVf1Lb5+Ejsxs/OjNzVeQlI1FZvaMozN5NNTUcYp4NKYdvsRgbNIGvYkM9TJEsYaD2NBWBY1VfHjVBfd0Pbd4/dpGg5FP/VhpoS2bAuBudcJvIqxUpYvACCEwMzRpxSJiIL3KZGqOL7O+R+6pL38k3VsTVP3Q3TM/ze/QTHjYVuUT+NvFbc119IGMYMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png" srcset="/static/5884d457fe2181ab0e490a1460ab913c/36ca5/2021-12-08-webinaresver.png 200w, /static/5884d457fe2181ab0e490a1460ab913c/a3397/2021-12-08-webinaresver.png 400w, /static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png 800w, /static/5884d457fe2181ab0e490a1460ab913c/8537d/2021-12-08-webinaresver.png 1200w, /static/5884d457fe2181ab0e490a1460ab913c/1a152/2021-12-08-webinaresver.png 1600w, /static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>And read in the versioning series:</strong></p> <ul> <li><a href="/en/simple_events_versioning_patterns/">Simple patterns for events schema versioning</a></li> <li><a href="/en/how_to_do_event_versioning/">How to (not) do the events versioning?</a></li> <li><a href="/en/fun_with_json_serialisation/">Fun with serial JSON</a></li> <li><a href="/en/how_to_map_event_type_by_convention/">Mapping event type by convention</a></li> <li><a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">Let’s take care of ourselves! Thoughts on compatibility</a></li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Slim your aggregates with Event Sourcing!]]>https://event-driven.io/en/slim_your_entities_with_event_sourcing/https://event-driven.io/en/slim_your_entities_with_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4fb7e9b76a8d20656bdfafc7fea23634/8299d/2022-08-31-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAMEAv/EABcBAAMBAAAAAAAAAAAAAAAAAAACAwT/2gAMAwEAAhADEAAAAWqtwVgHj7P/xAAcEAABBAMBAAAAAAAAAAAAAAACAAEREgMTITH/2gAIAQEAAQUCNnvrI0WLtoUyPq//xAAWEQEBAQAAAAAAAAAAAAAAAAABADH/2gAIAQMBAT8BWMv/xAAWEQEBAQAAAAAAAAAAAAAAAAAAAUH/2gAIAQIBAT8B1H//xAAaEAACAgMAAAAAAAAAAAAAAAAAARExIUFh/9oACAEBAAY/Ak1pDxRZHBkn/8QAGhAAAwADAQAAAAAAAAAAAAAAAAERITFRcf/aAAgBAQABPyF7TUg6GHol5CZl0gmy9UspuH//2gAMAwEAAgADAAAAEEff/8QAGBEAAgMAAAAAAAAAAAAAAAAAAAERIYH/2gAIAQMBAT8Qcpweh//EABYRAQEBAAAAAAAAAAAAAAAAAAEAEf/aAAgBAgEBPxBDLYQb/8QAGxABAQADAQEBAAAAAAAAAAAAAREAITFRQWH/2gAIAQEAAT8Qc2iXodYFQ0yxT3BhQxdfZhHBOn91loRER7L3K6xFz//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4fb7e9b76a8d20656bdfafc7fea23634/c60e9/2022-08-31-cover.jpg" srcset="/static/4fb7e9b76a8d20656bdfafc7fea23634/37402/2022-08-31-cover.jpg 200w, /static/4fb7e9b76a8d20656bdfafc7fea23634/4cda9/2022-08-31-cover.jpg 400w, /static/4fb7e9b76a8d20656bdfafc7fea23634/c60e9/2022-08-31-cover.jpg 800w, /static/4fb7e9b76a8d20656bdfafc7fea23634/6c738/2022-08-31-cover.jpg 1200w, /static/4fb7e9b76a8d20656bdfafc7fea23634/56dca/2022-08-31-cover.jpg 1600w, /static/4fb7e9b76a8d20656bdfafc7fea23634/8299d/2022-08-31-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>In the traditional approach, what we get is what we store. Our write model is too often used directly as our read model</strong>, because the typical set of functionality in a system has a details view that exposes it all. Based on that, the user makes decisions and indicates the desired updates. Depending on how CRUDish is our preference, we either have a single update or a set of business operations.</p> <p><strong>Let’s say that we’re modelling the shopping cart aggregate known from <a href="/en/how_to_effectively_compose_your_business_logic/">the previous post</a>.</strong> We have the following requirements:</p> <ol> <li>The customer may only add a product to the shopping cart after opening it.</li> <li>When selecting and adding a product to the basket customer needs to provide the quantity chosen. The system calculates the product price based on the current price list.</li> <li>The customer may remove a product with a given price from the cart.</li> <li>The customer can confirm the shopping cart and start the order fulfilment process.</li> <li>The customer may cancel the shopping cart and reject all selected products.</li> <li>After shopping cart confirmation or cancellation, the product can no longer be added or removed from the cart.</li> </ol> <p>A single good picture tells more than a thousand words, right? There you have it.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5b1cda5e4b9051f6398977592a2c07d1/989a8/2022-08-31-storming.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACSUlEQVQ4y32Tz2sTQRTH9+/x6NmbN0HwIHjzKF4Fr71UPImihyJixKoVQ21UqCCiYFusbailhmotpaRtuskm2ST7K9nt7s7MR3aTTRNb/cKXmZ198+V933ujKaVI0F8VUikcX2D3BFHkAB2UX0eYZYRjIIRIGcdxyiiKUmY6WrLJmAiGQvFqxeDJQo19fRHHyhOU7tK4dx63cCON6UONkGFSqSCpYP8wFDBbbDC9ZLBXXaZl5nE37tN4cAHnzU2UlKmL9FoqAkoKlIz7gvwFqcBoexidHn7QxXWb+JaBV/lF2D4cieyCKIOsjd3X8isGqzstnPkJzJnrSOPniKWT0DsBm3qI6ezTqT/Fa80hdj4Rb75DuXW024XfzBcPaD2+zMGts8j9b6mUTGwM6yvT7wQfS00mX2/zdWubhl6gffgW+9FFjMkzyPIS2nrZpmJ2cX+8x12bRXqNYYHlgJlogrXdNjMLFbb1Xczqc5z6HPb8BNXcVSK9lNRQjfXseITGkR3FQJQ4EDWa1Rk86wNKRIRHAVJKNKkGAomlgc0EQiqqVoRuxYRhB6ijehXEQRFqJUTcxbX3OArq400ZncPjeQQ/UuS+VHj4WadiLNI2pwm2pmjeOYebuwQyHpu/jNpJa33BIFYUvpu8XG6g14vY7QK9nRdYuSu4+WsgwsEcyrRYauD0n4LJ6nZ9LKeHZ69zWH6GZ23gOxae3RoWVf21aqfNWvYMM0TBKk19iihYP45Bndq8/2aYMnmORzXMRhG/pw/+yRMZZoJ/AM8ZJYRsp5I9AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="event storming" title="event storming" src="/static/5b1cda5e4b9051f6398977592a2c07d1/a331c/2022-08-31-storming.png" srcset="/static/5b1cda5e4b9051f6398977592a2c07d1/36ca5/2022-08-31-storming.png 200w, /static/5b1cda5e4b9051f6398977592a2c07d1/a3397/2022-08-31-storming.png 400w, /static/5b1cda5e4b9051f6398977592a2c07d1/a331c/2022-08-31-storming.png 800w, /static/5b1cda5e4b9051f6398977592a2c07d1/989a8/2022-08-31-storming.png 968w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Our aggregate could look like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IList<span class="token punctuation">&lt;</span>ProductItem<span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Open</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> cartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCart</span><span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> clientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">;</span> ClientId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ClientId<span class="token punctuation">;</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>ProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddProduct</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Adding product for the cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAdded</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> ProductItems <span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">[</span>ProductItems<span class="token punctuation">.</span><span class="token function">IndexOf</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> existingProductItem <span class="token keyword">with</span> <span class="token punctuation">{</span> Quantity <span class="token operator">=</span> existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">+</span> newProductItem<span class="token punctuation">.</span>Quantity <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Removing product from the cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> ProductItems <span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">existingProductItem<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemRemoved</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItemToBeRemoved <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> ProductItems <span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">[</span>ProductItems<span class="token punctuation">.</span><span class="token function">IndexOf</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> existingProductItem <span class="token keyword">with</span> <span class="token punctuation">{</span> Quantity <span class="token operator">=</span> existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">-</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Confirming cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Status <span class="token operator">!=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Canceling cart in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Canceled<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">ShoppingCartStatus</span> <span class="token punctuation">{</span> Pending <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> Confirmed <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">,</span> Canceled <span class="token operator">=</span> <span class="token number">4</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItem</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProductId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Quantity <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Plus the base class for completeness:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Aggregate</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Version <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">NonSerialized</span></span><span class="token punctuation">]</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Queue<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> uncommittedEvents <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token function">DequeueUncommittedEvents</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> dequeuedEvents <span class="token operator">=</span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">Clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> dequeuedEvents<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Enqueue</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Nothing spectacular is happening here. If you have already tried DDD or Event Sourcing, then, for sure, you already saw such code.</p> <p><strong>In case that’s the first time you see it. In Event Sourcing, each business operation should result in one or more new business events.</strong> Events represent the facts and give us a tracing of the business process. Events are appended to the event stream. Each entity (aggregate) has its dedicated stream. That’s why we keep the cache of uncommitted events. We’re using them store all at once after finishing business logic.</p> <p><strong>To retrieve the current state, we need to load all the events and apply them one by one on the default object.</strong> That’s why we need to have <em>Apply</em> methods for each of them. Read more in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a>.</p> <p><strong>This already looks like a lot of code for a simple feature, but that’s not all.</strong> In Event Sourcing, we’re building read models based on events. This logic is called projection. We’d need to duplicate (more or less) the same logic for the details view.</p> <p>At least, that’s what we see if we’re coming from the state-oriented approach. In the long term, those views will differ; we may also build the fine-tuned smaller read models, but let’s take it out of the discussion for now. That’s a decent topic for another article.</p> <p><strong>If we conclude that we need to always start new features with doubled code in Event Sourcing, then that may look like complicated bikeshedding.</strong> That’d be the correct conclusion if it wasn’t entirely true.</p> <p>As I mentioned, we’re building our write model in-memory in Event Sourcing. Each time we want to run our business logic, we’re <em>rehydrating</em> our write model state from events. Our read models are placed in another storage (another table like Marten or another database like EventStoreDB). So we don’t need to tie them together. What’s more, we can also benefit from that split to simplify our write model.</p> <p><strong>The first step is to check all IFs in our business logic.</strong> If we do that, then we could see that:</p> <ul> <li>we don’t need all the status information. We just need information if the shopping cart is opened or not.</li> <li>there’s no need to keep the entire product item information and merge it to form an elegant single line for product items. That’s nice for the read model. To write model logic, we need to know if we have enough quantity before removing the product item. To do that, we just need to sum up the total amounts.</li> <li>we don’t have any logic around client id.</li> </ul> <p>By observing that, we could simplify our class into:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsOpened <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Open</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> cartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCart</span><span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> clientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">;</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> IsOpened <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddProduct</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Adding product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAdded</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> ProductItems<span class="token punctuation">[</span>newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">]</span> <span class="token operator">=</span> ProductItems<span class="token punctuation">.</span><span class="token function">GetValueOrDefault</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> newProductItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Removing product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">currentQuantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemRemoved</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItemToBeRemoved <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span> <span class="token operator">||</span> currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> ProductItems<span class="token punctuation">[</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">]</span> <span class="token operator">=</span> currentQuantity <span class="token operator">-</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Confirming closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Canceling closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>As we store information in events, we can safely remove redundant code from our shopping cart aggregate.</strong> As long as we keep all information in events, we can add them back once we have a new requirement that makes them needed for business logic. That’s the power of in-memory rebuild!</p> <p>That’s already smaller and cleaner. Yet, we can do better than that!</p> <p><strong>Let’s get rid of the base class!</strong>. How to do it? By returning events from the method handling business logic. Then we won’t need to cache event(s) in the base class. Of course, some may say that _“but then my aggregate will show that’s event-sourced!“. Ok, so what is the issue? If we want to invest in Event Sourcing or Event-Driven Design, then let’s not add artificial limitations and overhead just for the sake that <em>“some day it may not be event-sourced anymore”</em>. What will it look like afterwards?</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsOpened <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token punctuation">(</span>ShoppingCart<span class="token punctuation">,</span> ShoppingCartOpened<span class="token punctuation">)</span></span> <span class="token function">Open</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> clientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> shoppingCart <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> shoppingCart<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>shoppingCart<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">;</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> IsOpened <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProductItemAdded</span> <span class="token function">AddProduct</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Adding product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAdded</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> ProductItems<span class="token punctuation">[</span>newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">]</span> <span class="token operator">=</span> ProductItems<span class="token punctuation">.</span><span class="token function">GetValueOrDefault</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> newProductItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProductItemRemoved</span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Removing product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">currentQuantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemRemoved</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItemToBeRemoved <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span> <span class="token operator">||</span> currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> ProductItems<span class="token punctuation">[</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">]</span> <span class="token operator">=</span> currentQuantity <span class="token operator">-</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartConfirmed</span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Confirming closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartCanceled</span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Canceling closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>It didn’t slim the codebase enormously, but it already made it more explicit.</strong> It’s clearly visible that each method takes params and returns event(s). That makes code also easier to test and understand. Can we do something more? Yes, we can!</p> <p><strong>If we’re always calling only a single method from our aggregate during the request/command handling, we may conclude that we need to update the state.</strong> We’re not storing our updated write model anywhere. It only lives per business logic handling scope (e.g. command handler). The only reason why we would keep it is to be able to run more than one method changing state in the same scope. Which is not the best practice, aye?</p> <p><strong>Let’s remove explicit <em>Apply</em> method calls from the business logic code!</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsOpened <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartOpened</span> <span class="token function">Open</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> clientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">;</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> IsOpened <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProductItemAdded</span> <span class="token function">AddProduct</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Adding product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAdded</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> ProductItems<span class="token punctuation">[</span>newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">]</span> <span class="token operator">=</span> ProductItems<span class="token punctuation">.</span><span class="token function">GetValueOrDefault</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> newProductItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProductItemRemoved</span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Removing product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">currentQuantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemRemoved</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItemToBeRemoved <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span> <span class="token operator">||</span> currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> ProductItems<span class="token punctuation">[</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">]</span> <span class="token operator">=</span> currentQuantity <span class="token operator">-</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartConfirmed</span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Confirming closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartCanceled</span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Canceling closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We still need to keep <em>Apply</em> methods definition to be able to rebuild the state from events. Still, we made the next step by cutting the boilerplate and making the class definition shorter and thus more straightforward and self-explanatory. We could, in theory, finish at that stage, but oh well, we need to go deeper!</p> <p>**If we look closer, we may notice that our methods are pure functions right now. That means they’re not causing side effects (like modifying the shopping cart state). <strong>That makes them predictable.</strong> This is a huge benefit, as such methods are much easier to test. Noticing that, we may realise that we could even make those methods static.</p> <p>We may also realise that as we’re not changing the aggregate state and not doing other logic, we’re not getting a tremendous value from keeping the state and behaviour in the same class. Of course, encapsulation can help if we’re afraid that our colleague will accidentally modify the code. But if that’s the case in our project, don’t we have more substantial issues to fix?</p> <p><strong>Let’s see how the code will look after the split.</strong></p> <p>Our aggregate will become the entity:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsOpened <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">;</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> IsOpened <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> ProductItems<span class="token punctuation">[</span>newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">]</span> <span class="token operator">=</span> ProductItems<span class="token punctuation">.</span><span class="token function">GetValueOrDefault</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">+</span> newProductItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItemToBeRemoved <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span> <span class="token operator">||</span> currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> ProductItems<span class="token punctuation">[</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">]</span> <span class="token operator">=</span> currentQuantity <span class="token operator">-</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Business logic placed in the service:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartOpened</span> <span class="token function">Open</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> clientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItemAdded</span> <span class="token function">AddProduct</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Adding product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAdded</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItemRemoved</span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Removing product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">currentQuantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemRemoved</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartConfirmed</span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Confirming closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartCanceled</span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Canceling closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>This is just one step from the Decider pattern described in <a href="/en/how_to_effectively_compose_your_business_logic/">How to effectively compose your business logic</a>.</p> <p>We could go even further to close the deal. As we don’t intend to modify our shopping cart beside the current state rehydration code, we could model it as a <a href="/en/notes_about_csharp_records_and_nullable_reference_types/">record</a>. It will add a bit more allocations, so if you’re cautious about that, you don’t have to.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> IsOpened<span class="token punctuation">,</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> ProductItems <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCart</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> ProductItems <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>Key <span class="token operator">!=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token class-name">ProductItem<span class="token punctuation">.</span>ProductId <span class="token punctuation">?</span></span> p <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">KeyValuePair<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> p<span class="token punctuation">.</span>Value <span class="token operator">+</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>Quantity <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToDictionary</span><span class="token punctuation">(</span>ks <span class="token operator">=></span> ks<span class="token punctuation">.</span>Key<span class="token punctuation">,</span> vs <span class="token operator">=></span> vs<span class="token punctuation">.</span>Value<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> ProductItems <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>Key <span class="token operator">!=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token class-name">ProductItem<span class="token punctuation">.</span>ProductId <span class="token punctuation">?</span></span> p <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">KeyValuePair<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> p<span class="token punctuation">.</span>Value <span class="token operator">-</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>Quantity <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToDictionary</span><span class="token punctuation">(</span>ks <span class="token operator">=></span> ks<span class="token punctuation">.</span>Key<span class="token punctuation">,</span> vs <span class="token operator">=></span> vs<span class="token punctuation">.</span>Value<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartOpened</span> <span class="token function">Open</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> clientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItemAdded</span> <span class="token function">AddProduct</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Adding product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemAdded</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItemRemoved</span> <span class="token function">RemoveProduct</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Removing product to the closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> currentQuantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` and price was not found in cart."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentQuantity <span class="token operator">&lt;</span> productItemToBeRemoved<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Cannot remove </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>Quantity</span><span class="token punctuation">}</span></span><span class="token string"> items of Product with id `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productItemToBeRemoved<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">` as there are only $</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">currentQuantity</span><span class="token punctuation">}</span></span><span class="token string"> items in card"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemRemoved</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartConfirmed</span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Confirming closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartCanceled</span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>shoppingCart<span class="token punctuation">.</span>IsOpened<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Canceling closed cart is not allowed."</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>I typically keep entities together with events, making it an excellent documentation form.</strong> We could compare it with our Event Storming/Modeling results. See:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAdded</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemRemoved</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ConfirmedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> CanceledAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> IsOpened<span class="token punctuation">,</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> ProductItems <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCart</span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Dictionary<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> ProductItems <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>Key <span class="token operator">!=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token class-name">ProductItem<span class="token punctuation">.</span>ProductId <span class="token punctuation">?</span></span> p <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">KeyValuePair<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> p<span class="token punctuation">.</span>Value <span class="token operator">+</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>Quantity <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToDictionary</span><span class="token punctuation">(</span>ks <span class="token operator">=></span> ks<span class="token punctuation">.</span>Key<span class="token punctuation">,</span> vs <span class="token operator">=></span> vs<span class="token punctuation">.</span>Value<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> ProductItems <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>Key <span class="token operator">!=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token class-name">ProductItem<span class="token punctuation">.</span>ProductId <span class="token punctuation">?</span></span> p <span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">KeyValuePair<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span><span class="token punctuation">(</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> p<span class="token punctuation">.</span>Value <span class="token operator">-</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>Quantity <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToDictionary</span><span class="token punctuation">(</span>ks <span class="token operator">=></span> ks<span class="token punctuation">.</span>Key<span class="token punctuation">,</span> vs <span class="token operator">=></span> vs<span class="token punctuation">.</span>Value<span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span> <span class="token keyword">with</span> <span class="token punctuation">{</span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Ok, but how to store such code? With Marten is extremely simple. We could define following method:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">DocumentSessionExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token punctuation">{</span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">StartStream</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> documentSession<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">token</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">GetAndUpdate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> version<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span></span> handle<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token operator">=></span> documentSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">WriteToAggregate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> version<span class="token punctuation">,</span> stream <span class="token operator">=></span> stream<span class="token punctuation">.</span><span class="token function">AppendOne</span><span class="token punctuation">(</span><span class="token function">handle</span><span class="token punctuation">(</span>stream<span class="token punctuation">.</span>Aggregate<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>They’re simple extension methods that make our intention explicit. The biggest power is in <a href="">WriteToAggregate</a> method that:</p> <ul> <li>loads all the events from the stream,</li> <li>aggregate them by calling <em>Apply</em> methods by conventions,</li> <li>allow storing one or more events.</li> </ul> <p>I added the option to run the specific handler to run the business logic. The example usage could look like that (using Minimal Apis):</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCartService</span><span class="token punctuation">;</span> <span class="token comment">// (...)</span> app<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"api/clients/{clientId:guid}/shopping-carts/"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> Guid clientId <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> shoppingCartId <span class="token operator">=</span> CombGuidIdGeneration<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Add</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token function">Open</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Created</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/shopping-carts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">shoppingCartId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"api/shopping-carts/{agentId:guid}/products/{productId}"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span> <span class="token class-name">IDocumentSession</span> documentSession<span class="token punctuation">,</span> <span class="token class-name">Guid</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> productId<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromBody</span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">int</span></span> quantity<span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromHeader</span><span class="token attribute-arguments"><span class="token punctuation">(</span>Name <span class="token operator">=</span> <span class="token string">"If-Match"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token class-name"><span class="token keyword">string</span></span> eTag<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> documentSession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetAndUpdate</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token function">ToExpectedVersion</span><span class="token punctuation">(</span>eTag<span class="token punctuation">)</span><span class="token punctuation">,</span> current <span class="token operator">=></span> <span class="token function">AddProduct</span><span class="token punctuation">(</span>current<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItem</span><span class="token punctuation">(</span>productId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> quantity<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It may be harder to slimmer it more! But if you want to see that read more the <a href="/en/prototype_underestimated_design_skill/">follow-up article</a>.</p> <p>Not persuaded? Check more in the <a href="https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates">webinar recording</a>!</p> <p><a href="https://www.architecture-weekly.com/p/webinar-8-slim-down-your-aggregates"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABl0lEQVQoz43RWU6DQADG8SmNIgrVJm5xiRVKKda61K0WgbDMDAyIZdiM9cElHsHEd72AJ/EmnsloE22qD07+LzPJ7+HLAEVpN5u7SqNdl7YkUZVEVa636vWWJKripip+vYhisyFvK8rOdqvTkNskPH99eXp+egQuosMcGDuw78C+FxRh/5pEVzgooJfaiNqIOjhxcYK81MVJdHEJbc80EIAwHs11Yz+8pPltnN4k+V1a3If9a0xyiFMXJcNsSB2cQZz+wjAOogHN7+LsFkcDFA3I+RUiheulDk7GGscQUc3wT7rW7qHu96xIs9T2yV5HM6zwt//ZPJyNvKwmqgLPAwDeMu/9IQNfp3OoI1IMx//g0YuNKCL5ek0ReIGbEWxpg8g1UGIAAPv/w8XahjzDcUvzC5N8pcROcRzHMMy/MA6K1bVNlmWr1So7OVEul5kyA0pg/+gv7OLk+w8cSLGfS3JrYXFpeXmlMjvHCxVBmJ2e5g+OzTEM/Qyc9mB3pJ6GLSvSdaKf+aYZmGZgGJ/pRtDV0GinZ/gDu8efv/k2/20AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/4548b02bcf56a39ea74f712e3c29152d/a331c/2022-08-31-webinar.png" srcset="/static/4548b02bcf56a39ea74f712e3c29152d/36ca5/2022-08-31-webinar.png 200w, /static/4548b02bcf56a39ea74f712e3c29152d/a3397/2022-08-31-webinar.png 400w, /static/4548b02bcf56a39ea74f712e3c29152d/a331c/2022-08-31-webinar.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p><strong>To sum up. Thanks to its nature, Event Sourcing aggregates can be made slimmer.</strong> You don’t need to put all the data there, as you may not need it for business logic. Check your IFs to see what you actually need to check your invariants. Then, depending on your preferences around:</p> <ul> <li>control,</li> <li>encapsulation,</li> <li>simplicity,</li> </ul> <p>you may decide to use it one way or another. This approach can be also helpful to classical state-based approach. Read more in <a href="/en/how_events_can_help_on_making_state_based_approach_efficient">How events can help in making the state-based approach efficient</a>.</p> <p>I encourage you to try those different styles and play with them. An excellent way for that is the <a href="/en/introduction_to_event_sourcing/">Event Sourcing self-paced kit</a> that I prepared for you.</p> <p>For me, the simpler, the better. You can reduce the cognitive load and spend less time understanding your business logic.</p> <p>Which style do you choose?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How playing on guitar can help you to be a better developer?]]>https://event-driven.io/en/how_playing_on_guitar_helps_in_being_better_developer/https://event-driven.io/en/how_playing_on_guitar_helps_in_being_better_developer/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f7f1a5403efe3430c32f38612bea42b7/8299d/2022-08-24-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 62.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAGQAAAgMBAAAAAAAAAAAAAAAAAAIBAwQF/8QAFQEBAQAAAAAAAAAAAAAAAAAAAgH/2gAMAwEAAhADEAAAAcsIxdB2Cn//xAAaEAACAwEBAAAAAAAAAAAAAAAAAgMRIgEx/9oACAEBAAEFAnkZhnxZNkg9rh//xAAVEQEBAAAAAAAAAAAAAAAAAAAQIf/aAAgBAwEBPwGH/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAHhAAAgEDBQAAAAAAAAAAAAAAAAECESExMkJRcaH/2gAIAQEABj8Cd0o8CjHaaPShHswf/8QAGhABAAMBAQEAAAAAAAAAAAAAAQARIUFhUf/aAAgBAQABPyGrXBsdGBodlPqNUV55ELpa62L012f/2gAMAwEAAgADAAAAEIsf/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERUf/aAAgBAwEBPxB4Ij//xAAWEQADAAAAAAAAAAAAAAAAAAAAARH/2gAIAQIBAT8QVKz/xAAbEAEBAAIDAQAAAAAAAAAAAAABEQAhMUFRcf/aAAgBAQABPxCGQBi69MDRuQvIOfN3DzfqxxqLUnbKR2FW2O3Orddg5//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/f7f1a5403efe3430c32f38612bea42b7/c60e9/2022-08-24-cover.jpg" srcset="/static/f7f1a5403efe3430c32f38612bea42b7/37402/2022-08-24-cover.jpg 200w, /static/f7f1a5403efe3430c32f38612bea42b7/4cda9/2022-08-24-cover.jpg 400w, /static/f7f1a5403efe3430c32f38612bea42b7/c60e9/2022-08-24-cover.jpg 800w, /static/f7f1a5403efe3430c32f38612bea42b7/6c738/2022-08-24-cover.jpg 1200w, /static/f7f1a5403efe3430c32f38612bea42b7/56dca/2022-08-24-cover.jpg 1600w, /static/f7f1a5403efe3430c32f38612bea42b7/8299d/2022-08-24-cover.jpg 1680w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>The first time I did not become a musician was when my grandma tried to sign me to music school.</strong> During rehearsal, I stubbornly said I would not sing or repeat things played on the piano.</p> <p><strong>The first time I didn’t become a programmer was when I found a floppy disk with the Basic label.</strong> I found it in my new Amiga 500. Technically, it was not new; it was secondhand, so the previous owner left the disk. Dad said it was for programming. I threw it in the box and turned on the Lotus III.</p> <p><strong>The second time I did not become a musician was one I signed to the local music library.</strong> I hoped to learn more about music. Interjection, dear children, in the pre-Spotify era, the music library was a place where you could listen to, or even borrow, CDs and attention - cassettes. Like Spotify, but physical and free, you need to return the music you borrowed. I’ve noted the band name to start my journey, but it appeared that I made it wrong. I got a CD with the terrible music of the band with a similar name.</p> <p><strong>The fact that I did not become a programmer for the second time is related to cassettes that sounded like a Skrillex when played on a tape recorder.</strong> Unusual recorder; the one from the Commodore 64. We used it to play in the North&#x26; South. On this hardware, I almost became a programmer. One could become one by reading a book and then rewriting it. The book had a yellow cover, A4 format, and by typing it for 2 hours, you could play MMORPG for a few minutes. I remember deciding whether to run away from the wolf or not. Robert (the C64 owner) and I decided we would run away from it and this programming thing.</p> <p><strong>I was quite close to becoming a musician when we were forced in primary school to learn to play the flute.</strong> You had to buy plastic, foldable ones, and be careful during the lesson. We weren’t going to do that with my friend Gabriel. Instead of playing, we were chatting. Worse, we were supposed to have a flute play exam at the end of the school season. Fortunately, the teacher changed her mind. Both today and then, I can only play one number - Sirene. You know it: eeyo-eeyo!</p> <p><strong>In 1991, I almost looked like a musician.</strong> I cut my jeans on my knees. To this day, I do not know how my father let a 6-year-old walk around in such pants. At the time, I didn’t understand why I was doing it. Today I know - I wanted to be like Kurt Cobain. I returned to this idea in high school when I was qualified for the grungers group with flannel shirts and jeans. A proud team of acid drinkers.</p> <p><strong>Nor did I become a programmer when in the 7th grade, I decided to go to high school with Mathematics and Computer Science specialisation.</strong> I didn’t go on the humanistic path even though I loved reading. I didn’t like cramming. Math came to me loosely, so the choice was simple. In the 2nd year of high school, my friend gave me CD with Visual Studio and .NET 1.0. I didn’t know the reason even to open it. I prefer to play FIFA and Baldur’s Gate.</p> <p>Just as printing the first Christmas tree written in Turbo Pascal during the Computer Science lesson did not make me a programmer, the acoustic guitar I got as my 18th birthday gift did not make me a guitarist.</p> <p><strong>On the other hand, Flash animations made me a programmer.</strong> The Cancion del Mariachi learned by heart, without rhythm and technique, made me a guitarist. At least in the eyes of my high school friends and family. Have I already been them? Definitely not. It’s hard to be a programmer and guitarist while still being a shaky kid with inconsistency, but with a whole set of straw eagerness.</p> <p><strong>I also didn’t become a musician when I bought a harmonica in Lidl and started playing a lesson called a train.</strong> It was too much for my girlfriend. Nor did I become one when she bought me guitar lessons. The classes were fun, and the teacher was cool, but I wasn’t cool. I over-motivated. I wanted to make up for years of putting the guitar in the corner, months of coming back and weeks of self-learning at one time. I had to let go and cool down. Think about the topic and approach it more calmly.</p> <p><strong>I then realised that learning to play the guitar is very similar to learning programming.</strong> I believe that you can use the same tactics in mastering both. How come?</p> <ol> <li> <p><strong>It’s hard to become a self-taught musician.</strong> It is harder to find specific directions and choose techniques to learn. Even more difficult is to impose a routine on yourself. Routine and consistency are the most important here. Playing only on weekends or holidays won’t make you a guitarist. In the same way, you also cannot learn programming once a month by sitting down and saying hello to the world. How do you get to Carnegie Hall? Practice, practice, practice. Finding someone to inspire, still being careful, and questioning authorities is also worth finding someone to inspire. By looking at what those people are interested in and following their steps, you can cut the time spent on finding the things to learn. Assaf Levavy and his licknriff.com is a model example for guitar. Programming varies depending on the technology, but I think that no one failed to recommend Martin Fowler’s work.</p> </li> <li> <p><strong>Did I mention the consequence?</strong> You won’t achieve anything without consistency, tedious training of licks, chord progressions, UI forms, entities design, reading, searching and trying. After a break without playing, the fingers do not move as fast as before. In the same way, after the break in programming, the brain does not cycle as quickly as before. Without regular typing and coding, you might even get scared to set up the new empty project.</p> </li> <li> <p><strong>Keep your head open and try various approaches.</strong> I was never interested in practising chords. I wanted to smash solos like John Petrucci right away. No campfire songs. Later it turned out that, when I learned chords, it became easier to move through the fretboard. Likewise, warming your fingers up in solos stretches them and makes it easier to catch grips. The same goes for programming - even if you hate JavaScript and think SPA applications are Satan’s invention, give it a try. If you think functional programming is a dumb idea, give it a try and evaluate. It is worth observing and testing different programming concepts and various platforms. Even if you don’t do any commercial stuff in them and return to your beloved technology, I assure you that you will look at it a little wider.</p> </li> <li> <p><strong>Understanding theory helps.</strong> I know, I know. The theory may sound boring, but you won’t get far without it. Just as you can make the UI form without knowing how computers handle memory, you can learn a song by heart on the guitar. Yet, understanding the theory can help you to do it better. You might get the a-ha moment when you hear the click, and things are suddenly slowing down for you. For example, when you understand that instead of flying like stupid through the fretboard, it might be better just to arrange them in a chord. You will play with less effort and thus make it sound better. In the same way, understanding the memory pressure can make your UI forms faster and less buggy.</p> </li> <li> <p><strong>Find the balance.</strong> After playing the guitar for too long, your collarbone and wrist will start to hurt. You can outstretch your neck muscles and cannot play for weeks (checked!). The same is with programming. Your head, eyes and wrist can get sick too. Worse than physical pain is burnout, the desire to quit everything, and mental fatigue. Better to take a break. Don’t over-motivate yourself. Some things take time. After sleeping or just leaving the desk, you devise a solution to a problem you have been struggling with for several hours. Likewise, when you play the guitar after a day of break, it often turns out that the part of a song that you couldn’t get overcomes itself as if you were Van Halen.</p> </li> <li> <p><strong>Having a manual hobby helps to decrease the stress level.</strong> I’m sure your brain spins around much longer after a busy day of coding. In theory, you finished, but you’re still working in your head. Doing stuff that requires manual exercise and focus helps to stop thinking. In guitar, you must focus on the rhythm and licks to do it right. You stop thinking about other stuff by focusing on moving fingers on the fretboard. It helps to cool down. Even short exercise can do miracles. Don’t like playing on guitar? Try something else, woodwork, bakery, whatever you prefer. Just find something that will require your body to switch and focus on other stuff than coding.</p> </li> <li> <p><strong>Hobby is a great tool to detect our workaholism signs.</strong> Do you know that coal miners used to carry canaries? Canaries are tender birds, much more delicate than we humans. If the level of dangerous gases (such as CO) in the mine tunnels increased, the gases would kill the canary before killing the miners. Seeing that, miners could run away and save their lives. Yeah, humanity works only for humans. Still, if our hobby dies and we don’t have enough time for it, that’s a clear sign that we’re on the slippery slope to burnout. That’s the right moment to stop and find it and revise our work style.</p> </li> </ol> <p><strong>I am still not becoming a programmer, e.g. when I buy a book on Amazon and don’t read it.</strong> Likewise, I don’t become a musician when I find a tutorial for a Full House theme and stop learning it. However, I am a programmer when I manage to deliver a project or deliver a new OSS feature. I also happen to be a guitarist when I catch the rhythm and feel my fingers moving seamlessly or when someone tells me, “oh, I can see that you have practised”.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[What do the British writer and his fence have to do with Software Architecture?]]>https://event-driven.io/en/chesterton_fence_and_software_architecture/https://event-driven.io/en/chesterton_fence_and_software_architecture/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/05c836a4ca371536718da6f8c7e1f45c/332ff/2022-08-17-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAAD+klEQVQ4yx3O7VPTBQDA8d9d73rTC8syH8InBG3JGI7B2HiYsIk7QRRpwwdEBwY+MZDnISAgXoCCpQh3gOYZ5F1KOWcniYyHgSFzIEyGQQhIAj6UnuSrb1d/wecjtN8fZWb2BUmNI6wsfMASUx/uRf28n2XnvXQ70bWj2BzjdD9w0eucYubFG1odz7Dce8Lr12/pd47S2tXH1PQs8/P/ILR0PcQ1NsmTqWf8MTlHTfs03uWP+KhgiIV5A1h6xpiYmmZobI742hHO335K++Bzmq3DXLhioeBsI1XN/RSbJ+l2ziB0P3Ay9HgS1/gMjuFJhh8/4d3beQYnXlNn/ZNTF2/T1utkYPwN1sEZnBMveTj+N+aOR+TVW7nWOUa++RWVbW/o7HMg9PT103B3jNobDn5usVHbZMbcaqPHPsjzuTlcY0+xOUb4D+64Z8d6z8707Et+HX7HwCT0/P4XZT/doemqiaxcKcLV1gEialxoq50kXR4i6aKD1Hobl8xdXL5xlyZLGxZrL9c6R7je4aJrYALrfSe/WG9xtbmI8vNbSMlxI+HQhySlLUW4bhuhzOKi/u5jmvumuNn/jN9GX1HX4qTmpoOaW4P8eKeXlu52zjaepfZKKuXfaMgpXk5G0adkFC8hv/Jz8iskZJ0UI7TYhjBb+7nZ7uCHZguNzXc4UV7JYdNRTpQlUHp6N5mmDRSUulNStpysgkXklq6gsExMTqGMzGwNcXsCCdf4olVLENLLiogwxLA5LhJtbBAJKRIOZrlRXBFCYfl68suXcqrSg5KKdZRUiSg9I+PIoSB021Rs3qhko0KBQurFhvUeeHl8hhC6xZOYaF9iY3wxHgzi3GkdJSdDuXAujobaY+SckpF2zI/c4zJMxQHEf+nPpgAZmkA/pCJ3/MSeyKVrEYmWEKxehpAcG8mh3dHkHTGQk7yXKLWKzMRYzpsMpGbL2XfYiwNHvTiS6kNympivEiWEydYhXb8KH+9FyNWfELbDjf3pq0kwuSOo/L5A5e+NViUlPHAtwTJPtobJ+K40kcwEBSnxfqQYZRgMEvbqxRyI80cj90KtXEN0vCfahBUoty8jdK8b+rzVCGEKb8JDFGzfFIA+QoU+OhK1wpvqbD2Xju+h3rSTSuMOjidq0cWIWLFmAYtFH+Dl8zGhylUEaBazRrEAsXohun0rEXZGBP0/VMl92KpWog3xZfsmf7INoVQYo/jaGENFup5vs3dh3BXKrnAfTIYQDuvkxIVL2L1ZRPo+JRn7lRyI80FQB0pRKXyRiT3ZKJcQpQkgRqvEaAjmdFok188k0dGQhr0pB/v3WbRWJ3OlUEd1RhR1Jh1VqduoMkaRpg8kMtiDfwF1kMHmyTAGugAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/05c836a4ca371536718da6f8c7e1f45c/a331c/2022-08-17-cover.png" srcset="/static/05c836a4ca371536718da6f8c7e1f45c/36ca5/2022-08-17-cover.png 200w, /static/05c836a4ca371536718da6f8c7e1f45c/a3397/2022-08-17-cover.png 400w, /static/05c836a4ca371536718da6f8c7e1f45c/a331c/2022-08-17-cover.png 800w, /static/05c836a4ca371536718da6f8c7e1f45c/332ff/2022-08-17-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Gilbert Keith Chesterton was a British writer from the turn of the 19th and 20th centuries.</strong> As for his times, we would call him an influencer or a maven. He had a lot of opinions and did not hesitate to share them. He wrote about 80 books, several hundred poems, 200 short stories, 4,000 essays and several plays. Plus countless letters, for example, to C. S. Lewis. Yes, <em>the Narnia guy</em>. In 1929, in his book <em>“The Thing ”</em>, he wrote:</p> <p><strong><em>“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.”</em></strong></p> <p>Chesterton was a professional writer. Even though he wasn’t writing programs, the quote sounds almost like programming.</p> <p>In our work, we regularly find a place where we wonder <em>what the author had in mind</em>. Not once, not twice, after removing the meaningless IF, it turned out that we got an angry call from the client informing us that some critical feature was not working anymore. It appears that the IF, which we considered useless, served something.</p> <p>Rudy Tomjanovich, after defending the NBA title in 1995, despite many hardships, told: <em><a href="https://www.youtube.com/watch?v=dTyP7I8X4UY">“Don’t ever underestimate the heart of a champion!”</a>.</em> I would paraphrase that as:</p> <p><strong><em>Don’t ever underestimate the value of working software</em></strong></p> <p>Disregarding existing code can come from many sources:</p> <ul> <li>overconfidence and too much trust in our abilities.</li> <li>lack of knowledge inside our company, e.g. not existing documentation or people who wrote this functionality are gone.</li> <li>besieged fortress syndrome or <a href="https://en.wikipedia.org/wiki/Not_invented_here">Not invented here</a> attitude, where <em>“what’s not ours is worse”</em>.</li> <li>time pressure and insufficient code analysis skills.</li> <li>lack of experience and awareness of the potential consequences.</li> <li>laziness.</li> <li>shedding responsibility based on “it’s not me, it’s my predecessors!”</li> </ul> <p><strong>Of course, I don’t want to play a symmetrist.</strong> The fact that something has been here for years doesn’t mean it should stay longer.</p> <p>What I want to say, and what I think Chesterton wanted to say, is that <strong>to make sure we don’t mess up anything, we need to understand why something is here before removing it.</strong></p> <p>It is not that simple, of course. Sometimes we have to be pragmatic because we don’t have enough time to analyze. In that case, it may be okay to add yet another IF, but only if we plan to investigate it afterwards.</p> <p>Of course, the analysis is much easier when we have documentation and someone to ask questions. Yet, we usually don’t have such a chance in such a situation.</p> <p><strong>How to start?</strong></p> <ol> <li> <p><strong>Write down our understanding of the process and go to various people (preferably from business departments) and ask what they think.</strong> Do they see any shortcomings? Does it work differently? People usually find it harder to explain the whole thing. Also, most of the time, there is not a single person with knowledge about the entire process. It’s best to do a little roundtrip, collect answers, update your description, or confront contradictions. It is much easier to redact something written than do it from scratch. <a href="/pl/fifteen_tips_on_how_to_run_meetings_effectively/">Having a basis for discussions makes it more effective</a>.</p> </li> <li> <p><strong>Having our vision of how it should work, write automatic tests.</strong> Of course, it’s easier to say than do. Writing unit tests can be challenging because the code often is in a not testable state. I usually start with so-called <em>end to end</em> tests, i.e. integration tests that verify the entire flow. (see also more in <a href="/pl/i_tested_on_production/">“I tested it on production and I’m not ashamed of it”</a>). It’s best to start with the critical path (the one that always needs to work) and then gradually expand to include edge case tests. And here is the most important thing! <strong>Let’s try not to change the implementation until we describe it with tests. We want to understand the code to not have unpleasant surprises.</strong> If our test doesn’t work, we either didn’t do a decent job in its setup or don’t understand the functionality. Usually, test results can show us that a business process is one thing and the code another. We should go back to the drawing board with our business and discuss our findings. Like Kent Beck said, <a href="https://www.youtube.com/watch?v=3gib0hKYjB0">“Make the change easy, make the easy change”</a>. First, we write tests that correspond to reality. Only then can we change it.</p> </li> <li> <p><strong>Write down all the arrangements and principles in a document describing the facts.</strong> It is best to do this as a Markdown document and put it in the repository we are changing. In the future, this will be our basis for understanding, showing what has altered and our decision log. We will not have to maintain such documentation. We write down our knowledge on the day we do it. We can append subsequent changes in our state of knowledge or decisions in the form of separate records. Of course, we can also have a unified document that combines all of this, but this is a nice-to-have. It will be a secondary document representing the current state of the art. The decision log will be our source of truth and the story of why we made these decisions instead of others. Our successors (or us in the future) won’t need to wonder, <em>“what is this fence for?”</em> I wrote about it in <a href="/pl/how_to_successfully_do_documentation_without_maintenance_burden/">“How to successfully do documentation without a maintenance burden?”</a>. Keeping the documentation in the form of Markdown also gives us an additional advantage. We can send it to other teams, asking them to share their opinions. Let’s give them a few days. They may share important insights. It also increases the transparency of decisions and builds an appropriate work culture in the company. In the worst case, if they don’t say anything, they waste their chance to speak up and accept what we wrote down.</p> </li> <li> <p><strong>Having all that, we can decide to tear down the fence or strengthen it.</strong> Knowing (at least roughly) the actual state, we can proceed to remove or refactor the code. Usually, new things are popping out again, but that’s fine. Once that happens, we go back to the previous steps and refactor the documentation and tests. It is critical not to do this together with new changes. Nothing is worse for the reviewer than trying to understand a mammoth pull request where refactorings are mixed with the new changes. Let’s do things sequentially and in isolation. We’ll thank ourselves for that in the future. Or even sooner than we think. If we show that we’re not changing implementation but only expanding tests and docs to enhance our understanding, then such changes will usually be accepted smoothly. Read more in <a href="/pl/should_programmers_productivity_be_shown_in_code_formatting/">“Should a programmer’s creativity be shown in code formatting?”</a>.</p> </li> </ol> <p><strong>Risk is always there, but we must learn to live with it and manage it consciously (see <a href="/pl/the_risk_of_ignoring_risks/">“The risk of ignoring risks”</a>).</strong> By taking these steps, we make our lives easier and make our decisions more confident, predictable and safer. It will also <a href="/pl/why_are_we_afraid_of_our_decisions/">help to not be afraid of making decisions</a>.</p> <p>By dividing the process into smaller chunks, we also increase the chance of completing the changes. We won’t end up with massive, never-ending refactoring. You’ve been there already, haven’t you?</p> <p>When you hear someone (e.g. yourself) telling <strong><em>That’s stupid, let’s remove it or refactor it!</em></strong>, demand an explanation as to what exactly it is wrong and needs to be replaced, removed or repaired.</p> <p>Just like Chesterton did with the fence.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Why are we afraid of our decisions?]]>https://event-driven.io/en/why_are_we_afraid_of_our_decisions/https://event-driven.io/en/why_are_we_afraid_of_our_decisions/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d865af1a156db30066ef2334ff2e2e74/332ff/2022-08-10-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAADJUlEQVQ4yz3RTWgcdRjH8UHJwYBaCkUQWkUQvOhNevHowTcaQURQqKIYXHqwh1CkodVK1Yo2EhVFjcRUQoIpNdUaTKhNk7JNTbrJLtvJzu687bzP7FuSKigi+crzj9vDj//szvDh+f8eLWu0iJOMKE5V5Fn9lkQJjUaLza0/aXW2yLImjaxJs9lW/8spabU6253OJhsbN//RBBQojBKVLiqYvDPWK3xyfIDzI6dpNhpkWUuhAnYjaLu9sd3pbKElaUNBQRir3EIV2OajwcP88O4hzr/Xz9zEV3Q2/yBNMzVtN/+j2+32BppM08WCINpBw5g0bWCaJscOPkNx6luuj3/Gl0deIc0EyxQq30iyrLktqFxdk2n8IMLzQ3Wq+KG61tXFBU69/hxrY0PUZyf55mg/lm3TyDKSJCVJslsRWK6uQMHqXqBO9eyHtP/+l/lLc+Se3I9+9gu8+XMcf/lp1lbyNDdvEoUhcZwQRSlxvIPLlJp0Jphb91UE80wb67sxRgcH6Hv0Aa5Pfk5SzvPmU48wO/wWrSgiTBJCqSiI1QIFlD416Uwg26ljWQ5e2mRl/AyluzVyPRpDg/0sjbyDf22GsRM5pvZq/DVwAE+698OdBNEOmjbQ/CDBcX1My6Fas6j7EfmLM5Q/PsLwE/tx1uaZPHiA/PAJnNkzVJ59GOuDw9RurKsFel6gOpdFyoI1z49x3ICaaVMxqliuT3FpkeLPo5R+/Z7q3FmOahon+x4nK8zhL0zjmzp1qUfiejuoTBmnaHUvwrI9jKqNrhsYlsvybzOsTQzRsla5/HaOi7dp5B66H31hmnZpHr+0hOuFOE5dVeUKKlcPYzTbDTGsOnrFpFyuoBsWi7O/UP5plMqVCxx77EHmem/njb27uDDyIc38OazCFex6iG07WLarIntQYM0J0GsORd1graRTLOn8vrzKyo/jDL7ax4v33sHXvb303XcX7x96Ae/yFOvFAqbjUTMtVZX077ieurZm2B5lw6IgUKHEteVVCqV1Lk1P89q+PUzs7uHGnh5e2n0nz9+zi6ufnqbqhRiVKhWjhlE1FdoF/wOuAKkDblbdDgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/d865af1a156db30066ef2334ff2e2e74/a331c/2022-08-10-cover.png" srcset="/static/d865af1a156db30066ef2334ff2e2e74/36ca5/2022-08-10-cover.png 200w, /static/d865af1a156db30066ef2334ff2e2e74/a3397/2022-08-10-cover.png 400w, /static/d865af1a156db30066ef2334ff2e2e74/a331c/2022-08-10-cover.png 800w, /static/d865af1a156db30066ef2334ff2e2e74/332ff/2022-08-10-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>I don’t know if it will work well in production!</strong></p> <p><strong>Will it scale?</strong></p> <p><strong>What if we get too many users during Black Friday?</strong></p> <p>Do you know those questions? I asked them myself; what’s more, I hear it regularly when talking to people. I am also often asked: how do I find the answers, and how do I deal with them? Well, I don’t have all the answers. Sometimes I’m wrong; sometimes I’m right. And that’s okay.</p> <p><strong>These discussions take place on two levels: technical and metaphysical.</strong> The metaphysical one is dangerous because it is not tangible. Warning! Stay with me after what you’re about to read. It may sound like coaching gibberish, but it’ll be made actionable later. I promise! So…</p> <p><strong>To assess what actions we should take, we should first consider where our fear of making a decision comes from.</strong> Sometimes, it is a justified fear, but most often, it’s not.</p> <p>Too often, it comes from issues directly related to our company’s organisational culture (or lack thereof). Too often, we’re not rewarded for good but punished for bad. The logical conclusion is to do nothing. Then we will have peace of mind.</p> <p>Sometimes, it is a justified fear, but most often, it’s not.</p> <p>Another problem is, what I call, a <strong>broken contract between business and development.</strong> We don’t believe that business will give us time to correct our mishaps. Business doesn’t trust us that the next refactoring will actually be needed and not just moving the code from one place to another.</p> <p><strong>Fear may come from us not being experienced enough.</strong> In our industry, we usually face new problems every day (or following in a coaching tone: <em>challenges</em>). We rarely have an identical case to solve. And even if we do, it often turns out that we were too fast in our assumptions. The similarity was only superficial, and the problem turned out completely different. A small detail made it so.</p> <p><strong>All of these fears are real. If we focus on them, they will overwhelm us. How do you break this vicious circle?</strong></p> <p>Let’s start by asking ourselves three questions:</p> <ol> <li>How is it working now (e.g. <em>on paper</em>)?</li> <li>What must happen for our plan to succeed?</li> <li>What’s the worst case if our plan fails?</li> </ol> <p>These are elementary questions, but they will put us on the right track.</p> <p><strong>The first one will allow us to immediately understand the problem’s nature and how business deals with it now.</strong> This is helpful for many reasons. First, it will enable us to understand the current flows. From our company, if we’re modelling existing processes or competitive solutions if the subject is entirely new to us. It often turns out that whatever we prepare will be better than what we have now. Realising that can already take a lot of pressure off. By diving into existing solutions, we’ll learn the characteristics of the problem. What traffic should we expect, what frequency, and what are the expectations? They can often be unrealistic, but the confrontation with the current state will allow us to reduce it to honest discussions, not metaphysical ones. We should also investigate and understand alternative paths. Often a <a href="/en/what_texting_ex_has_to_do_with_event_driven_design/">compensating action</a> (e.g. a refund) will be much easier to implement. It will also be much easier to maintain than the complicated byzantine code constructs trying hard to protect us from the inevitable.</p> <p><strong>The second question I took from <a href="https://hanselminutes.com/790/leslie-lamport-in-partnership-with-acm-bytecast">Leslie Lamport</a>.</strong> As programmers, we usually look for problems and edge cases. And that’s okay because it allows us to understand our situation better. However, searching for the problem can easily change into a neverending story. Not this one with <a href="https://www.youtube.com/watch?v=2WN0T-Ee3q4">fabulous Limahl hair</a>, but with going down the rabbit hole and never coming close to the correct solution. <strong>Focusing on what must happen to make our process successful is better.</strong> From this perspective, we can break the problem into smaller fragments and plan all the necessary guards for each step. We will still have to explore the edge situations, but only those that can actually stand in our way. It will help us not go crazy.</p> <p><strong>The third question is our sanity check. By understanding the worst thing can happen, we will ensure that our problem is as significant and complex as it seems.</strong> It is worth taking a breath here, stopping for a while, and talking with domain experts but also people from other departments. We should not only crunch it inside our team but also confront colleagues from other teams. <a href="https://en.wikipedia.org/wiki/Rubber_duck_debugging">Even talk to the rubber duck.</a> If we fall into tunnel thinking, our functionality seems to be the biggest priority. When too focused on solving the case, we may turn out critical thinking. If we ask around, it may be that our problem is not as important and challenging as we thought. We can also realise that a simpler solution will be better in this case.</p> <p><strong>In addition to the questions, here are a few tips:</strong></p> <ol> <li> <p><strong>Don’t assume that your design will stand the test of time.</strong> If it is successful, it will change. Design is not meant to be engraved in the rock; it is to be useful to the given scale of the problem. If the scale or problem changes, <strong>the design has to evolve!</strong></p> </li> <li> <p><strong>The best decision is not always the right decision.</strong> We make decisions at a given point in time. We are unable to predict the future. At least I am not. Sometimes something that looked like the best decision turns out to be wrong over time. We have to live with it. I wrote about this in an article on <a href="/en/the_risk_of_ignoring_risks/">The risk of ignoring risks</a>. The best we can is good enough.</p> </li> <li> <p><strong>We should gather expected metrics and verify them.</strong> Every time I hear <em>“is it gonna scale?”</em> combined with the answer <em>“I don’t know”</em> to the counter-question: <em>“what does this mean for you?”</em> a little unicorn dies. If we don’t know how something should scale, we should find that out. Let’s check how competing solutions work and assume something if we cannot find it. Without it, we again fall into metaphysical deliberations in discussions and lock ourselves in a decision clinch. Let’s define the metrics and verify them later. Only then will we know whether we will meet expectations or not.</p> </li> <li> <p><strong>We constantly verify our assumptions.</strong> The sooner we catch a wrong decision, the greater the chance we will be able to correct it or fix the consequences. The sooner we update the plan, the better. If we know the risks that may occur and have pre-developed rescue plans, we should analyse them regularly.</p> </li> </ol> <p><strong>TLDR: Let’s not be afraid to make difficult decisions.</strong> If we discuss them well, they may not be all that difficult. Let’s learn to live with them and accept that we may be wrong. We make decisions at a given point in time. However, this does not release us from analysing and assessing whether we made a mistake and correcting our mishaps.</p> <p>Check also the follow up post, explaining how to make decisions based on the existing state: <a href="/en/chesterton_fence_and_software_architecture">What do the British writer and his fence have to do with architecture?</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Straightforward Event Sourcing with TypeScript and NodeJS]]>https://event-driven.io/en/type_script_node_Js_event_sourcing/https://event-driven.io/en/type_script_node_Js_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/24c37d5f1cf03b47c126c731a50d273d/332ff/2022-08-03-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAAsTAAALEwEAmpwYAAABwElEQVQ4y42U3UvbYBTG+wd6s79gV4OxK5WhMOtUhl6IG4PBcHqxboN5oSBTwS/spLPqRG27zbqyGo20Tftam9R+pbXJb3SNpc1i9JCX5D3nycN5znvO68Ey0zRb79YyjM69zd+OvzGP5e0gva854T32YE3TKCcT6GmFYvwPpVOJaiaDnkxQOTuldC6jRULoInN7hu1OXQgKsWO00AG53W3U8CGqFCWzt8PFzzC6JJH9FqAonbgT2iU06tSwelzmx8xzVv1DLLzsJuxfwKCMYdQcZXucDoXGU4drs0jON4n0oIvoxgtKYpnF8UccLL4Cyv9K73wottQNs4ZeiVEpBMiffKEQmUO/DACHFC7WiO/Ndv7jRNgENGVWVYX8tg9t6yOJ1WnSm+9JrbzjKviZ1PIbjr7OtfCukm/qlv8VRO5/TGqwG2WoB2W4l9RIL+lxL0c9Dwl+eN2qszuhFVSjfpSxZ4jRAcTYAOmRPpLDT8mOeon1P+F4c8kirN9F2Py+1q+oCYGRzaH+jrI1NcH3T28pyTLV7CWGpcS1sW/r/pxIsr8+S8g/j+mAu5fk9pnt6EuT/+b4zgwdLwubRLeZ/wukoShqnWinmQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/24c37d5f1cf03b47c126c731a50d273d/a331c/2022-08-03-cover.png" srcset="/static/24c37d5f1cf03b47c126c731a50d273d/36ca5/2022-08-03-cover.png 200w, /static/24c37d5f1cf03b47c126c731a50d273d/a3397/2022-08-03-cover.png 400w, /static/24c37d5f1cf03b47c126c731a50d273d/a331c/2022-08-03-cover.png 800w, /static/24c37d5f1cf03b47c126c731a50d273d/332ff/2022-08-03-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>In the last two articles, I explained <a href="/en/how_to_effectively_compose_your_business_logic/">how to organise your business logic effectively</a>, showing how proper typing and composition can help to achieve that.</strong> That came with an explanation of the <em>Decider</em> pattern and a few other like <em>Either</em> and <em>Maybe</em> data structures. The samples used Object-Oriented languages, so <a href="en/how_to_effectively_compose_your_business_logic/">Java</a> and <a href="/en/union_types_in_csharp">C#</a>. That was intentional, as I wanted to show that <em><a href="https://www.youtube.com/watch?v=2u1zK8AaHic">if you could make it there, you could make it anywhere</a></em>.</p> <p><strong>Today, I’d like to wrap up the series and show how this could look in the language with native support for union types. I decided to use <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types">TypeScript</a>.</strong> Yup <a href="">I was also a hater of it</a> some time ago, but now I think it’s a cool language that, together with NodeJS, makes development quick and cuts the amount of boilerplate compared to other environments.</p> <p><strong>If you didn’t read previous articles, let’s do a recap:</strong></p> <ul> <li>we’re modelling the shopping cart process. You can open it, add or remove the product from it and confirm or cancel.</li> <li>we want to have a little help from our friends: types. They should reflect all the possible states. We want compiler checks to prevent dummy mistakes and reduce the number of unit tests.</li> <li>All of that will allow us to express our business logic in our code better. It’ll also give us better trust in our code.</li> </ul> <p><strong>The logic looks like this:</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5b1cda5e4b9051f6398977592a2c07d1/989a8/2022-08-03-storming.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACSUlEQVQ4y32Tz2sTQRTH9+/x6NmbN0HwIHjzKF4Fr71UPImihyJixKoVQ21UqCCiYFusbailhmotpaRtuskm2ST7K9nt7s7MR3aTTRNb/cKXmZ198+V933ujKaVI0F8VUikcX2D3BFHkAB2UX0eYZYRjIIRIGcdxyiiKUmY6WrLJmAiGQvFqxeDJQo19fRHHyhOU7tK4dx63cCON6UONkGFSqSCpYP8wFDBbbDC9ZLBXXaZl5nE37tN4cAHnzU2UlKmL9FoqAkoKlIz7gvwFqcBoexidHn7QxXWb+JaBV/lF2D4cieyCKIOsjd3X8isGqzstnPkJzJnrSOPniKWT0DsBm3qI6ezTqT/Fa80hdj4Rb75DuXW024XfzBcPaD2+zMGts8j9b6mUTGwM6yvT7wQfS00mX2/zdWubhl6gffgW+9FFjMkzyPIS2nrZpmJ2cX+8x12bRXqNYYHlgJlogrXdNjMLFbb1Xczqc5z6HPb8BNXcVSK9lNRQjfXseITGkR3FQJQ4EDWa1Rk86wNKRIRHAVJKNKkGAomlgc0EQiqqVoRuxYRhB6ijehXEQRFqJUTcxbX3OArq400ZncPjeQQ/UuS+VHj4WadiLNI2pwm2pmjeOYebuwQyHpu/jNpJa33BIFYUvpu8XG6g14vY7QK9nRdYuSu4+WsgwsEcyrRYauD0n4LJ6nZ9LKeHZ69zWH6GZ23gOxae3RoWVf21aqfNWvYMM0TBKk19iihYP45Bndq8/2aYMnmORzXMRhG/pw/+yRMZZoJ/AM8ZJYRsp5I9AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="event storming" title="event storming" src="/static/5b1cda5e4b9051f6398977592a2c07d1/a331c/2022-08-03-storming.png" srcset="/static/5b1cda5e4b9051f6398977592a2c07d1/36ca5/2022-08-03-storming.png 200w, /static/5b1cda5e4b9051f6398977592a2c07d1/a3397/2022-08-03-storming.png 400w, /static/5b1cda5e4b9051f6398977592a2c07d1/a331c/2022-08-03-storming.png 800w, /static/5b1cda5e4b9051f6398977592a2c07d1/989a8/2022-08-03-storming.png 968w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Ah, I forgot, I’m using Event Sourcing in the examples, because why not? It’s cool, but if you don’t want to do it, all those composition patterns and strategies also apply to the traditional approach.</p> <p>Let’s start with the event definition:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartCanceled'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> canceledAt<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p><strong>We’re defining <em>ShoppingCartEvent</em> type as a union of other types.</strong> It means that it’ll be either one or another. As underneath TypeScript, once we compile it, there is a good Ye Olde JavaScript we need to somehow be able to find the difference between our objects to know the type. To do that, we’re using the <em>type</em> field. Thanks to that, each type will have its own unique type name that will differentiate it from others. If you’re afraid of <em>stringly typing</em>, don’t worry, you’ll get all IntelliSense and compiler checks guarding you against any stupid mistakes.</p> <p>The field discriminating our union type doesn’t have to be named <em>type</em>. In fact, we can use any field for that. For instance, we could name it <em>status</em> and use it like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCart</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Empty'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Pending'</span><span class="token punctuation">;</span> id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Confirmed'</span><span class="token punctuation">;</span> id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Canceled'</span><span class="token punctuation">;</span> id<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> canceledAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>This is our shopping cart states definition. As this is a simple state machine, the status is unique for each state.</p> <p>Now, to show you that, we could also use that for more advanced stuff, like pattern matching. Let’s see how we could build the current state from a list of events. I described this pattern in my other articles:</p> <ul> <li><a href="/en/partial_typescript/">Why Partial<Type> is an extremely useful TypeScript feature?</a></li> <li><a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a></li> </ul> <p><strong>In short, we’re taking a sequence of events, the default object and applying them gradually.</strong> For that, we need the function that will return the new state from the current one and an event. This function is usually called <em>when</em>, <em>apply</em> or <em>evolve</em> and can look like that:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> evolve <span class="token operator">=</span> <span class="token punctuation">(</span> currentState<span class="token operator">:</span> ShoppingCart<span class="token punctuation">,</span> event<span class="token operator">:</span> ShoppingCartEvent <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCart <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartOpened'</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentState<span class="token punctuation">.</span>status <span class="token operator">!=</span> <span class="token string">'Empty'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> currentState<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>clientId<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'Pending'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentState<span class="token punctuation">.</span>status <span class="token operator">!=</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> currentState<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token function">addProductItem</span><span class="token punctuation">(</span> currentState<span class="token punctuation">.</span>productItems<span class="token punctuation">,</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentState<span class="token punctuation">.</span>status <span class="token operator">!=</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> currentState<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token function">removeProductItem</span><span class="token punctuation">(</span> currentState<span class="token punctuation">.</span>productItems<span class="token punctuation">,</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem <span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentState<span class="token punctuation">.</span>status <span class="token operator">!=</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> currentState<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'Confirmed'</span><span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>confirmedAt<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'ShoppingCartCanceled'</span><span class="token operator">:</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentState<span class="token punctuation">.</span>status <span class="token operator">!=</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token keyword">return</span> currentState<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> status<span class="token operator">:</span> <span class="token string">'Canceled'</span><span class="token punctuation">,</span> canceledAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>canceledAt<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> _<span class="token operator">:</span> <span class="token builtin">never</span> <span class="token operator">=</span> event<span class="token punctuation">;</span> <span class="token keyword">return</span> currentState<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>As you can see, I’m using pattern matching here based on the event type and returning the new state. To get the final state, we need to call:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> events <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token generic-function"><span class="token function">readStream</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartEvent<span class="token operator">></span></span></span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> streamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> shoppingCart <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">reduce</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCart<span class="token operator">></span></span></span><span class="token punctuation">(</span> evolve<span class="token punctuation">,</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Empty'</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Getting the current state is the first step to being able to run the business logic. Event Sourcing is no different in that. <strong>Let’s define the set of operations that we could do for our shopping cart.</strong> Again we’ll use the union type definition.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartCommand</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'OpenShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'RemoveProductItemFromShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ConfirmShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CancelShoppingCart'</span><span class="token punctuation">;</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>What would you say for a bit of business logic? Yeah, let’s go; that’s what we’re here for. As our Shopping Cart is not the most sophisticated one, we’ll use some pattern-matching magic again.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> decide <span class="token operator">=</span> <span class="token punctuation">(</span> <span class="token punctuation">{</span> type<span class="token punctuation">,</span> data<span class="token operator">:</span> command <span class="token punctuation">}</span><span class="token operator">:</span> ShoppingCartCommand<span class="token punctuation">,</span> shoppingCart<span class="token operator">:</span> ShoppingCart <span class="token punctuation">)</span><span class="token operator">:</span> ShoppingCartEvent <span class="token operator">|</span> ShoppingCartEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'OpenShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>status <span class="token operator">!=</span> <span class="token string">'Empty'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> ShoppingCartErrors<span class="token punctuation">.</span><span class="token constant">CART_ALREADY_EXISTS</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartOpened'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> command<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token operator">:</span> command<span class="token punctuation">.</span>clientId<span class="token punctuation">,</span> openedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toJSON</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> ShoppingCartErrors<span class="token punctuation">.</span><span class="token constant">CART_IS_ALREADY_CLOSED</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemAddedToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> command<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> command<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'RemoveProductItemFromShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> ShoppingCartErrors<span class="token punctuation">.</span><span class="token constant">CART_IS_ALREADY_CLOSED</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">assertProductItemExists</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>productItems<span class="token punctuation">,</span> command<span class="token punctuation">.</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ProductItemRemovedFromShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> command<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> command<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'ConfirmShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> ShoppingCartErrors<span class="token punctuation">.</span><span class="token constant">CART_IS_ALREADY_CLOSED</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartConfirmed'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> command<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> confirmedAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toJSON</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'CancelShoppingCart'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>status <span class="token operator">!==</span> <span class="token string">'Pending'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> ShoppingCartErrors<span class="token punctuation">.</span><span class="token constant">CART_IS_ALREADY_CLOSED</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ShoppingCartCanceled'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> command<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> canceledAt<span class="token operator">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toJSON</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> _<span class="token operator">:</span> <span class="token builtin">never</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">throw</span> ShoppingCartErrors<span class="token punctuation">.</span><span class="token constant">UNKNOWN_COMMAND_TYPE</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Based on the type of command, we’re running certain business logic. And as a result, returning the new event represents the recorded business fact.</p> <p><em>“That’s naive! What if we have real business logic?!”</em> - you could say. My answer is that <strong>composition is the king</strong>. If you carefully check the code snippets, you may notice that I already cheated on you. I skipped some of the helper functions for product items. Here they are:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">interface</span> <span class="token class-name">ProductItem</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">const</span> addProductItem <span class="token operator">=</span> <span class="token punctuation">(</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> newProductItem<span class="token operator">:</span> ProductItem <span class="token punctuation">)</span><span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity <span class="token punctuation">}</span> <span class="token operator">=</span> newProductItem<span class="token punctuation">;</span> <span class="token keyword">const</span> currentProductItem <span class="token operator">=</span> <span class="token function">findProductItem</span><span class="token punctuation">(</span>productItems<span class="token punctuation">,</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>currentProductItem<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token operator">...</span>productItems<span class="token punctuation">,</span> newProductItem<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newQuantity <span class="token operator">=</span> currentProductItem<span class="token punctuation">.</span>quantity <span class="token operator">+</span> quantity<span class="token punctuation">;</span> <span class="token keyword">const</span> mergedProductItem <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> newQuantity <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> productItems<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">===</span> productId <span class="token operator">?</span> mergedProductItem <span class="token operator">:</span> pi <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> removeProductItem <span class="token operator">=</span> <span class="token punctuation">(</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> newProductItem<span class="token operator">:</span> ProductItem <span class="token punctuation">)</span><span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity <span class="token punctuation">}</span> <span class="token operator">=</span> newProductItem<span class="token punctuation">;</span> <span class="token keyword">const</span> currentProductItem <span class="token operator">=</span> <span class="token function">assertProductItemExists</span><span class="token punctuation">(</span> productItems<span class="token punctuation">,</span> newProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newQuantity <span class="token operator">=</span> currentProductItem<span class="token punctuation">.</span>quantity <span class="token operator">-</span> quantity<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>newQuantity <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> productItems<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">!==</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> mergedProductItem <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> newQuantity <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> productItems<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">===</span> productId <span class="token operator">?</span> mergedProductItem <span class="token operator">:</span> pi <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> findProductItem <span class="token operator">=</span> <span class="token punctuation">(</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> productId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">)</span><span class="token operator">:</span> ProductItem <span class="token operator">|</span> <span class="token keyword">undefined</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> productItems<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">===</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> assertProductItemExists <span class="token operator">=</span> <span class="token punctuation">(</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity <span class="token punctuation">}</span><span class="token operator">:</span> ProductItem <span class="token punctuation">)</span><span class="token operator">:</span> ProductItem <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> current <span class="token operator">=</span> <span class="token function">findProductItem</span><span class="token punctuation">(</span>productItems<span class="token punctuation">,</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>current <span class="token operator">||</span> current<span class="token punctuation">.</span>quantity <span class="token operator">&lt;</span> quantity<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> ShoppingCartErrors<span class="token punctuation">.</span><span class="token constant">PRODUCT_ITEM_NOT_FOUND</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> current<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>As you can see, by the proper composition of small functions and types, we can reduce the <a href="https://en.wikipedia.org/wiki/Cognitive_load">cognitive load</a>, so the amount of knowledge we need to adhere to at once.</p> <p>We could also generalise our processing logic and define the following types:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Event<span class="token operator">&lt;</span> EventType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> EventData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> EventType<span class="token punctuation">;</span> data<span class="token operator">:</span> EventData<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Command<span class="token operator">&lt;</span> CommandType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> CommandData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> CommandType<span class="token punctuation">;</span> data<span class="token operator">:</span> CommandData<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Decider<span class="token operator">&lt;</span> State<span class="token punctuation">,</span> CommandType <span class="token keyword">extends</span> Command<span class="token punctuation">,</span> EventType <span class="token keyword">extends</span> Event <span class="token operator">></span></span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token function-variable function">decide</span><span class="token operator">:</span> <span class="token punctuation">(</span>command<span class="token operator">:</span> CommandType<span class="token punctuation">,</span> state<span class="token operator">:</span> State<span class="token punctuation">)</span> <span class="token operator">=></span> EventType <span class="token operator">|</span> EventType<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token function-variable function">evolve</span><span class="token operator">:</span> <span class="token punctuation">(</span>currentState<span class="token operator">:</span> State<span class="token punctuation">,</span> event<span class="token operator">:</span> EventType<span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token function-variable function">getInitialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> State<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>At first glance, this may look a bit cryptic. Yet it just tells that event is an object that has:</p> <ul> <li><em>type</em> property, that’s <em>string</em></li> <li><em>data</em> property that’s a <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type">record</a>, so a complex object.</li> </ul> <p>The same goes for commands.</p> <p><strong>Having that, we can define a <a href="https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider">Decider</a>.</strong> It’s a pattern defined by <a href="https://twitter.com/thinkb4coding">Jérémie Chassaing</a> that shows how to organise our business logic in the event-driven world. It takes the following functions:</p> <ul> <li><em>decide</em>, that runs the command’s business logic on the current state returning event or sequence of events as a result,</li> <li><em>evolve</em>, that applies the event on the state,</li> <li><em>getInitialState</em>, which returns the default, not initialised state.</li> </ul> <p>We already defined everything, so let’s group it into a specific function:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> decider<span class="token operator">:</span> Decider<span class="token operator">&lt;</span> ShoppingCart<span class="token punctuation">,</span> ShoppingCartCommand<span class="token punctuation">,</span> ShoppingCartEvent <span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> decide<span class="token punctuation">,</span> evolve<span class="token punctuation">,</span> <span class="token function-variable function">getInitialState</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token string">'Empty'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We could call it a day and finish now. Most of the composition techniques were discussed, but we won’t! Let’s try to go further and code the fully working Web API using those patterns.</p> <p><strong>Let’s start with the definition of the command handler.</strong> We’ll use <a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">Optimistic Concurrency with ETag</a> to guarantee that we’re making decisions based on the latest state.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> CommandHandler <span class="token operator">=</span> <span class="token operator">&lt;</span>State<span class="token punctuation">,</span> CommandType <span class="token keyword">extends</span> <span class="token class-name">Command</span><span class="token punctuation">,</span> EventType <span class="token keyword">extends</span> <span class="token class-name">Event</span><span class="token operator">></span><span class="token punctuation">(</span> <span class="token function-variable function">getEventStore</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> EventStoreDBClient<span class="token punctuation">,</span> <span class="token function-variable function">toStreamId</span><span class="token operator">:</span> <span class="token punctuation">(</span>recordId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">string</span><span class="token punctuation">,</span> decider<span class="token operator">:</span> Decider<span class="token operator">&lt;</span>State<span class="token punctuation">,</span> CommandType<span class="token punctuation">,</span> EventType<span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">async</span> <span class="token punctuation">(</span> recordId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> command<span class="token operator">:</span> Command<span class="token punctuation">,</span> eTag<span class="token operator">:</span> ETag <span class="token operator">|</span> <span class="token keyword">undefined</span> <span class="token operator">=</span> <span class="token keyword">undefined</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>AppendResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> streamId <span class="token operator">=</span> <span class="token function">toStreamId</span><span class="token punctuation">(</span>recordId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> events <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token generic-function"><span class="token function">readStream</span><span class="token generic class-name"><span class="token operator">&lt;</span>EventType<span class="token operator">></span></span></span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> streamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> state <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">reduce</span><span class="token generic class-name"><span class="token operator">&lt;</span>State<span class="token operator">></span></span></span><span class="token punctuation">(</span> decider<span class="token punctuation">.</span>evolve<span class="token punctuation">,</span> decider<span class="token punctuation">.</span><span class="token function">getInitialState</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newEvents <span class="token operator">=</span> decider<span class="token punctuation">.</span><span class="token function">decide</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> toAppend <span class="token operator">=</span> <span class="token builtin">Array</span><span class="token punctuation">.</span><span class="token function">isArray</span><span class="token punctuation">(</span>newEvents<span class="token punctuation">)</span> <span class="token operator">?</span> newEvents <span class="token operator">:</span> <span class="token punctuation">[</span>newEvents<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">appendToStream</span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> streamId<span class="token punctuation">,</span> eTag<span class="token punctuation">,</span> <span class="token operator">...</span>toAppend<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Command handler is a wrapper function that takes event store client factory and decider and records id to stream id mapper. It returns the handler function that takes the record id, command and <code class="language-text">ETag</code>. It:</p> <ul> <li>reads the stream events,</li> <li>based on them, builds the current state using the decider’s evolve function,</li> <li>then, it runs <em>decide</em> function that returns event(s) as a result,</li> <li>those events are appended back to the stream.</li> </ul> <p>Btw. this wrapping technique is called <a href="https://en.wikipedia.org/wiki/Currying">currying</a>.</p> <p>Implementation with EventStoreDB could look like this:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">let</span> eventStore<span class="token operator">:</span> EventStoreDBClient<span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">getEventStore</span> <span class="token operator">=</span> <span class="token punctuation">(</span>connectionString<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>eventStore<span class="token punctuation">)</span> <span class="token punctuation">{</span> eventStore <span class="token operator">=</span> EventStoreDBClient<span class="token punctuation">.</span><span class="token function">connectionString</span><span class="token punctuation">(</span> connectionString <span class="token operator">??</span> <span class="token string">'esdb://localhost:2113?tls=false'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> eventStore<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> readStream <span class="token operator">=</span> <span class="token generic-function"><span class="token function">async</span> <span class="token generic class-name"><span class="token operator">&lt;</span>EventType <span class="token keyword">extends</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span> eventStore<span class="token operator">:</span> EventStoreDBClient<span class="token punctuation">,</span> streamId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> events <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> <span class="token punctuation">{</span> event <span class="token punctuation">}</span> <span class="token keyword">of</span> eventStore<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">readStream</span><span class="token generic class-name"><span class="token operator">&lt;</span>EventType<span class="token operator">></span></span></span><span class="token punctuation">(</span>streamId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>event<span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span> events<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>EventType<span class="token operator">></span><span class="token punctuation">{</span> type<span class="token operator">:</span> event<span class="token punctuation">.</span>type<span class="token punctuation">,</span> data<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> events<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">AppendResult</span> <span class="token operator">=</span> <span class="token operator">|</span> <span class="token punctuation">{</span> nextExpectedRevision<span class="token operator">:</span> ETag<span class="token punctuation">;</span> successful<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">|</span> <span class="token punctuation">{</span> expected<span class="token operator">:</span> ETag<span class="token punctuation">;</span> actual<span class="token operator">:</span> ETag<span class="token punctuation">;</span> successful<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> appendToStream <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span> eventStore<span class="token operator">:</span> EventStoreDBClient<span class="token punctuation">,</span> streamId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> eTag<span class="token operator">:</span> ETag <span class="token operator">|</span> <span class="token keyword">undefined</span><span class="token punctuation">,</span> <span class="token operator">...</span>events<span class="token operator">:</span> Event<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>AppendResult<span class="token operator">></span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> eventStore<span class="token punctuation">.</span><span class="token function">appendToStream</span><span class="token punctuation">(</span> streamId<span class="token punctuation">,</span> events<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>jsonEvent<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> expectedRevision<span class="token operator">:</span> eTag <span class="token operator">?</span> <span class="token function">getExpectedRevisionFromETag</span><span class="token punctuation">(</span>eTag<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token constant">NO_STREAM</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> successful<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> nextExpectedRevision<span class="token operator">:</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>nextExpectedRevision<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>error <span class="token keyword">instanceof</span> <span class="token class-name">WrongExpectedVersionError</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> successful<span class="token operator">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> expected<span class="token operator">:</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>error<span class="token punctuation">.</span>expectedVersion<span class="token punctuation">)</span><span class="token punctuation">,</span> actual<span class="token operator">:</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>error<span class="token punctuation">.</span>actualVersion<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">throw</span> error<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>See also ETag helpers for brevity:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">WeakETag</span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">W/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token builtin">string</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ETag</span> <span class="token operator">=</span> WeakETag <span class="token operator">|</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> WeakETagRegex <span class="token operator">=</span> <span class="token regex"><span class="token regex-delimiter">/</span><span class="token regex-source language-regex">W\/"(\d+.*)"</span><span class="token regex-delimiter">/</span></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token keyword">enum</span> ETagErrors <span class="token punctuation">{</span> <span class="token constant">WRONG_WEAK_ETAG_FORMAT</span> <span class="token operator">=</span> <span class="token string">'WRONG_WEAK_ETAG_FORMAT'</span><span class="token punctuation">,</span> <span class="token constant">MISSING_IF_MATCH_HEADER</span> <span class="token operator">=</span> <span class="token string">'MISSING_IF_MATCH_HEADER'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">const</span> isWeakETag <span class="token operator">=</span> <span class="token punctuation">(</span>etag<span class="token operator">:</span> ETag<span class="token punctuation">)</span><span class="token operator">:</span> etag <span class="token keyword">is</span> WeakETag <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> WeakETagRegex<span class="token punctuation">.</span><span class="token function">test</span><span class="token punctuation">(</span>etag<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> getWeakETagValue <span class="token operator">=</span> <span class="token punctuation">(</span>etag<span class="token operator">:</span> ETag<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> WeakETagRegex<span class="token punctuation">.</span><span class="token function">exec</span><span class="token punctuation">(</span>etag<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token operator">===</span> <span class="token keyword">null</span> <span class="token operator">||</span> result<span class="token punctuation">.</span>length <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> ETagErrors<span class="token punctuation">.</span><span class="token constant">WRONG_WEAK_ETAG_FORMAT</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> result<span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> toWeakETag <span class="token operator">=</span> <span class="token punctuation">(</span>value<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span><span class="token operator">:</span> WeakETag <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">W/"</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> getExpectedRevisionFromETag <span class="token operator">=</span> <span class="token punctuation">(</span>eTag<span class="token operator">:</span> ETag<span class="token punctuation">)</span><span class="token operator">:</span> bigint <span class="token operator">=></span> <span class="token function">assertUnsignedBigInt</span><span class="token punctuation">(</span><span class="token function">getWeakETagValue</span><span class="token punctuation">(</span>eTag<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>We need to go deeper! Or higher. As we have such a nice command handler, let’s try to sprinkle it with HTTP on top of it to be able to handle <a href="https://expressjs.com/en/guide/routing.html">ExpressJS routing</a>.</strong></p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> HTTPHandler <span class="token operator">=</span> <span class="token operator">&lt;</span>Command<span class="token punctuation">,</span> RequestType <span class="token keyword">extends</span> <span class="token class-name">Request</span> <span class="token operator">=</span> Request<span class="token operator">></span><span class="token punctuation">(</span> <span class="token function-variable function">handleCommand</span><span class="token operator">:</span> <span class="token punctuation">(</span> recordId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> command<span class="token operator">:</span> Command<span class="token punctuation">,</span> eTag<span class="token operator">?</span><span class="token operator">:</span> ETag <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>AppendResult<span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span> <span class="token function-variable function">mapRequest</span><span class="token operator">:</span> <span class="token punctuation">(</span> request<span class="token operator">:</span> RequestType<span class="token punctuation">,</span> <span class="token function-variable function">handler</span><span class="token operator">:</span> <span class="token punctuation">(</span>recordId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> command<span class="token operator">:</span> Command<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">async</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> RequestType<span class="token punctuation">,</span> response<span class="token operator">:</span> Response<span class="token punctuation">,</span> next<span class="token operator">:</span> NextFunction<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">mapRequest</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>recordId<span class="token punctuation">,</span> command<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">handleCommand</span><span class="token punctuation">(</span> recordId<span class="token punctuation">,</span> command<span class="token punctuation">,</span> <span class="token function">getETagFromIfMatch</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">mapToResponse</span><span class="token punctuation">(</span>response<span class="token punctuation">,</span> recordId<span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">next</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> getETagFromIfMatch <span class="token operator">=</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> Request<span class="token punctuation">)</span><span class="token operator">:</span> ETag <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> etag <span class="token operator">=</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'if-match'</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>etag <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> ETagErrors<span class="token punctuation">.</span><span class="token constant">MISSING_IF_MATCH_HEADER</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> etag<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> mapToResponse <span class="token operator">=</span> <span class="token punctuation">(</span> response<span class="token operator">:</span> Response<span class="token punctuation">,</span> recordId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> result<span class="token operator">:</span> AppendResult<span class="token punctuation">,</span> urlPrefix<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>result<span class="token punctuation">.</span>successful<span class="token punctuation">)</span> <span class="token punctuation">{</span> response<span class="token punctuation">.</span><span class="token function">sendStatus</span><span class="token punctuation">(</span><span class="token number">412</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> response<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'ETag'</span><span class="token punctuation">,</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>nextExpectedRevision<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span>nextExpectedRevision <span class="token operator">==</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">sendCreated</span><span class="token punctuation">(</span>response<span class="token punctuation">,</span> recordId<span class="token punctuation">,</span> urlPrefix<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> response<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> sendCreated <span class="token operator">=</span> <span class="token punctuation">(</span> response<span class="token operator">:</span> Response<span class="token punctuation">,</span> createdId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> urlPrefix<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token keyword">void</span> <span class="token operator">=></span> <span class="token punctuation">{</span> response<span class="token punctuation">.</span><span class="token function">setHeader</span><span class="token punctuation">(</span> <span class="token string">'Location'</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>urlPrefix <span class="token operator">??</span> response<span class="token punctuation">.</span>req<span class="token punctuation">.</span>url<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>createdId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">201</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token operator">:</span> createdId <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Again we’re doing function returning function. Or, to be precise, function returning function returning function. I know how that sounds, but here we go:</p> <ul> <li>The main one takes the command handler function with the record identifier and command and returns the function.</li> <li>The next one takes the HTTP request and has a callback function that we should call to handle the request to command mapping.</li> <li>The last one is a classical routing function. It takes a request, gets ETag from it, maps the request to command and calls the handler method from the parameter of the initial function. After that, it does dance around the proper HTTP response status handling. It returns <em>201</em> when a new record was created, <em>412</em> if there was an optimistic concurrency check failed, and <em>200</em> otherwise. It also wraps it with a try/catch block to do proper response disposal.</li> </ul> <p>If that looks too complex, then I hope that merging all of that will make it clearer.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> handleCommand <span class="token operator">=</span> <span class="token generic-function"><span class="token function">CommandHandler</span><span class="token generic class-name"><span class="token operator">&lt;</span> ShoppingCart<span class="token punctuation">,</span> ShoppingCartCommand<span class="token punctuation">,</span> ShoppingCartEvent <span class="token operator">></span></span></span><span class="token punctuation">(</span> getEventStore<span class="token punctuation">,</span> <span class="token punctuation">(</span>shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">shopping_cart-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>shoppingCartId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> decider <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> on <span class="token operator">=</span> <span class="token generic-function"><span class="token function">HTTPHandler</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartCommand<span class="token operator">></span></span></span><span class="token punctuation">(</span>handleCommand<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We’re composing at first command handler and using it to build a generic HTTP handler. We can use it to define our endpoints as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> router <span class="token operator">=</span> <span class="token function">Router</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Open Shopping cart</span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> handle<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">uuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">handle</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'OpenShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token punctuation">,</span> clientId<span class="token operator">:</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>clientId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Add Product Item</span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/:shoppingCartId/product-items'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> handle<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">handle</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'AddProductItemToShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>productId<span class="token punctuation">)</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token function">assertPositiveNumber</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Remove Product Item</span> router<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/:shoppingCartId/product-items'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> handle<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">handle</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'RemoveProductItemFromShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>query<span class="token punctuation">.</span>productId<span class="token punctuation">)</span><span class="token punctuation">,</span> quantity<span class="token operator">:</span> <span class="token function">assertPositiveNumber</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>query<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Confirm Shopping Cart</span> router<span class="token punctuation">.</span><span class="token function">put</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/:shoppingCartId'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> handle<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">handle</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'ConfirmShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Confirm Shopping Cart</span> router<span class="token punctuation">.</span><span class="token function">delete</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/:shoppingCartId'</span><span class="token punctuation">,</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> handle<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCartId <span class="token operator">=</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">handle</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'CancelShoppingCart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token function">assertNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>As you can see, all of the routes definition just need to define the mapping logic from the request to command and call the callback. The rest will be handled internally. Thanks to the composition and type definition, we’re still getting compiler checks and are guarded by that.</p> <p>See, besides going a bit wild with command handling definition, we just used simple types and specific types definitions. That helped us to reflect the business process in our code and keep our logic short and concise. TypeScript gave us a more succinct and less verbose code definition. NodeJS, cutting the boilerplate of the WebApi frameworks.</p> <p>I encourage you to play with that and get your own opinion. Source codes are available here: <a href="https://github.com/oskardudycz/EventSourcing.NodeJS/pull/21">https://github.com/oskardudycz/EventSourcing.NodeJS/pull/21</a>.</p> <p>This approach can be also helpful to classical state-based approach. Read more in <a href="/en/ow_events_can_help_on_making_state_based_approach_efficient">How events can help in making the state-based approach efficient</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Union types in C#]]>https://event-driven.io/en/union_types_in_csharp/https://event-driven.io/en/union_types_in_csharp/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/434eb05e8dbf58e96b66532185173039/332ff/2022-07-27-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADYUlEQVQozwFWA6n8AGlxe3aDkm96h3d+i3yHlHiGk4iRnUZTVHuGjaauvK2ywp2jsJSbppCXpYiRnoKLmHN9iXuEkXyEkV9pcQB2gpBseoh7hZBweIGBhY5cYWVLT1Q2OztXX2mHkJqKlaCMlJ6BipGCi5SEipR4go1veoR6hpJndH1danEAZXB6aXR/b3mDYGp2eWhXmnNFtIhchlYpFBMVQkRNfYiSdYKJiZCVdX6Cc3h9gIeOgIqSeIOLZnF4YWx1AIaToYeToHeGkn2HkpFzTNi9lN+xf3Y/DCgWCRILCkFAQ2Zydn+KknZ9gnh+hXZ6f4OLlIWOmHN9iXmDkwBUYGdVYWZVZ3SOjIqpjGjq59GbjnRkOxWGTBxHLhsVDg1AODiUf3OLgn+EiY2AhY2Ij5h9h5FcZWxXYWgAWmZtcXqCcX2JfX151se01sa+2dPC4dCwsYpcKxwPDg0OJRkXk1Mlh0kdh109f3h2d36HZGptVlxeU1xfAGt4gnWBinB8hYuSlruqmrqKhtDGt9jIqXJhSwAAAQ4NEBQRFY5ZMHVFI3U+GHtdSXuDjISKk3+GjG93fgBndIBte4Z3hI56ho2XlpGciXSKc1eKcEsWFBICBAgKCg0AAQloTTN/UiyGUSlzVkWIkp+RmaSEjJR2f4YAcH6IdYKMeISNf4qViZOeUVNVQjwwQ0I6BQgNDgwPJhwWBwgNNS0oWz4ojlovX0Ize4aSfISNd36Gb3mDAG16hGp3gW97hXR/io2Vn5CapGNtfCcxNxUeIi4lH2A9IgQECREPFVY3IKJ3UWBSTKmqr87V4snP38DH2ACWoq2os727xtDT2+Ps8Pb8/v/+//+He3A4ODleVEyFZkw8Mi4/MS+PeWa6noPGxceekIj////t8/zq7v0A6vD96vb98vz/+/3/+vv+9fX4/P//v62XuLCqzsfBu6OLpZKGdmFXppWGrpqO////o5eQtbCs5+764uf1AOPp9+Ts+Or1//X7//////D2/Ovx+ayYf8/IveLZzaOQeMC0ppR8armomsW2rP///+z1+LSys7i4v+Ho+gDg5vPa3+rP1dvKzdC5ube1tbCYkolrW0WDem2KgXRxZlZ/c2RxaV9+cF+dkoW5wcXg6PHu9P/d4/Xb3/MwX7WeEkSlawAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/434eb05e8dbf58e96b66532185173039/a331c/2022-07-27-cover.png" srcset="/static/434eb05e8dbf58e96b66532185173039/36ca5/2022-07-27-cover.png 200w, /static/434eb05e8dbf58e96b66532185173039/a3397/2022-07-27-cover.png 400w, /static/434eb05e8dbf58e96b66532185173039/a331c/2022-07-27-cover.png 800w, /static/434eb05e8dbf58e96b66532185173039/332ff/2022-07-27-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In the article <a href="/en/how_to_effectively_compose_your_business_logic/">“How to effectively compose your business logic”</a>, I explained how explicit types definition can help in making our codebase closer to the business domain, also predictable and secured. I used Java as an example, as its origins from the Object-Oriented paradigm. So it was a choice like with New York: <em>“if you can make it here, you can make it everywhere”</em>. I was asked, okay, but how to model that in C#? Fasten seat belts, as this will be a rough ride! Here I come with an answer!</p> <p>Even though F# supports (discriminated) union types, C# lags behind. It is the feature I’m waiting for the most, but I’m getting more syntactic sugar with each release instead. Java allows to model them via <a href="https://openjdk.org/jeps/409">sealed interface</a>. Let’s see if we could model it similarly. We could get close, but not precisely the same.</p> <p>Let’s see how we could express the Shopping Cart that can be either empty (not initialised), pending, confirmed or cancelled.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">EmptyShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCart</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">PendingShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> ProductItems <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCart</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ConfirmedShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> ProductItems<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ConfirmedAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCart</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CanceledShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> ProductItems<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> CanceledAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCart</span></span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>I defined the type for each of the possible shopping cart states.</p> <p><strong>We want to limit the following changes made by other devs:</strong></p> <ul> <li>instantiating the base shopping cart definition,</li> <li>extending it freely (or, in fact, accidentally) to limit hacking.</li> </ul> <p>We cannot make it <em>sealed</em> to, but by making the default constructor <em>private</em>, we’re effectively blocking others from creating other derived classes, and the code won’t compile.</p> <p><strong>We’d also like proper pattern matching to make processing easier.</strong> Let’s define events that can happen for our shopping cart to show that better:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> ConfirmedAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">DateTimeOffset</span> CanceledAt <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ShoppingCartEvent</span></span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">ShoppingCartEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, it’s the same tactic. To demonstrate how pattern matching works, let me show it using the example of rebuilding the state from events.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> Empty <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EmptyShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCart</span> <span class="token function">Evolve</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> state<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartEvent</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> @<span class="token keyword">event</span> <span class="token keyword">switch</span> <span class="token punctuation">{</span> ShoppingCartOpened <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId<span class="token punctuation">)</span> <span class="token operator">=></span> state <span class="token keyword">is</span> <span class="token class-name">EmptyShoppingCart <span class="token punctuation">?</span></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PendingShoppingCart</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> ProductItems<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token punctuation">:</span> state<span class="token punctuation">,</span> ProductItemAddedToShoppingCart <span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> pricedProductItem<span class="token punctuation">)</span> <span class="token operator">=></span> state <span class="token keyword">is</span> <span class="token class-name">PendingShoppingCart</span> pendingShoppingCart <span class="token punctuation">?</span> pendingShoppingCart <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> pendingShoppingCart<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>pricedProductItem<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">:</span> state<span class="token punctuation">,</span> ProductItemRemovedFromShoppingCart <span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> pricedProductItem<span class="token punctuation">)</span> <span class="token operator">=></span> state <span class="token keyword">is</span> <span class="token class-name">PendingShoppingCart</span> pendingShoppingCart <span class="token punctuation">?</span> pendingShoppingCart <span class="token keyword">with</span> <span class="token punctuation">{</span> ProductItems <span class="token operator">=</span> pendingShoppingCart<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>pricedProductItem<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">:</span> state<span class="token punctuation">,</span> ShoppingCartConfirmed <span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> confirmedAt<span class="token punctuation">)</span> <span class="token operator">=></span> state <span class="token keyword">is</span> <span class="token class-name">PendingShoppingCart</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> productItems<span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ConfirmedShoppingCart</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> productItems<span class="token punctuation">,</span> confirmedAt<span class="token punctuation">)</span> <span class="token punctuation">:</span> state<span class="token punctuation">,</span> ShoppingCartCanceled <span class="token punctuation">(</span>_<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> canceledAt<span class="token punctuation">)</span> <span class="token operator">=></span> state <span class="token keyword">is</span> <span class="token class-name">PendingShoppingCart</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> clientId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">var</span></span> productItems<span class="token punctuation">)</span> <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CanceledShoppingCart</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> productItems<span class="token punctuation">,</span> canceledAt<span class="token punctuation">)</span> <span class="token punctuation">:</span> state<span class="token punctuation">,</span> _ <span class="token operator">=></span> state <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">ShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> </code></pre></div> <p>In Event Sourcing, we get all the events for a specific entity (stream). Starting from an empty state, we’re applying events to it one by one. For instance:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> events <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartEvent<span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> currentState <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token function">Aggregate</span><span class="token punctuation">(</span>ShoppingCart<span class="token punctuation">.</span>Empty<span class="token punctuation">,</span> ShoppingCart<span class="token punctuation">.</span>Evolve<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then we can use such a state for our business logic. You can read more in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a>.</p> <p>Coming back to our pattern matching. As you can see, it allows pretty sophisticated logic, yet it’s not 100% bulletproof. As we cannot fully restrict the number of implementations of the abstract class (only to assembly), we won’t get the compiler checks if we use all branches in the switch code. It is possible in Java, but it seems that we C# devs cannot have all the good things. Yet, of course, we could (and should) check that via unit tests. We can also try to write a custom analyser to get it through static code analysis.</p> <p><strong>The same pattern we can use for other places.</strong> Not only for domain code. Let’s say we have some text formatter that can either take a date or <a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a>.</p> <p>We could model the input parameter as a single type:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TextFormatter</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">record</span> <span class="token class-name">FormattedValue</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token function">FormattedValue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">DateTime</span><span class="token punctuation">(</span> <span class="token class-name">DateTimeOffset</span> Value <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">FormattedValue</span></span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Milliseconds</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">long</span></span> Value <span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">FormattedValue</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then we could define the formatting method:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TextFormatter</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">Format</span><span class="token punctuation">(</span><span class="token class-name">FormattedValue</span> formatted<span class="token punctuation">)</span> <span class="token operator">=></span> formatted <span class="token keyword">switch</span> <span class="token punctuation">{</span> FormattedValue<span class="token punctuation">.</span><span class="token function">DateTime</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> FormattedValue<span class="token punctuation">.</span><span class="token function">Milliseconds</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>formatted<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"That should never happen!"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The method call will look as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">TextFormatter<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">TextFormatter<span class="token punctuation">.</span>FormattedValue<span class="token punctuation">.</span>DateTime</span><span class="token punctuation">(</span>dateTime<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TextFormatter<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">TextFormatter<span class="token punctuation">.</span>FormattedValue<span class="token punctuation">.</span>Milliseconds</span><span class="token punctuation">(</span>milliseconds<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That doesn’t look bad, right? We have strong typing, clear information of where types should be used, pattern matching, etc. Yet, this may be tedious to define a class for each possible parameter’s permutation. It may become a dull job to do each time.</p> <p><strong>What else could we do? Is there more help from the C# language?</strong></p> <p>Not quite, but let’s try to bend it harder!</p> <p><strong>We could try to use <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples">Tuples</a></strong> They’re types that allow providing more than one value for a specific object and defining them in-place. How we could use it in our formatting sample?</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TextFormatter</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">Format</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> DateTime<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">long</span><span class="token punctuation">?</span></span> Milliseconds<span class="token punctuation">)</span> date<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>dateTime<span class="token punctuation">,</span> milliseconds<span class="token punctuation">)</span> <span class="token operator">=</span> date<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>dateTime<span class="token punctuation">.</span>HasValue <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>milliseconds<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>date<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"Either </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp"><span class="token keyword">nameof</span><span class="token punctuation">(</span>date<span class="token punctuation">.</span>DateTime<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string"> or </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp"><span class="token keyword">nameof</span><span class="token punctuation">(</span>date<span class="token punctuation">.</span>Milliseconds<span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string"> needs to be set"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token return-type class-name">dateTime<span class="token punctuation">.</span>HasValue <span class="token punctuation">?</span></span> dateTime<span class="token punctuation">.</span>Value<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> DateTimeOffset<span class="token punctuation">.</span><span class="token function">FromUnixTimeMilliseconds</span><span class="token punctuation">(</span>milliseconds<span class="token operator">!</span><span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>So instead of defining the class, we’re just grouping them and making them nullable. Then we could check which parameter is different from <em>null</em> and run the specific format logic.</p> <p>How to call such a function?</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">TextFormatter<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span><span class="token punctuation">(</span>dateTime<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TextFormatter<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> milliseconds<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That’s not great, as we need to provide the null for the other type we’re not using.</p> <p>Of course, we could add some helper methods, like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EitherExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token punctuation">(</span>TLeft<span class="token punctuation">?</span><span class="token punctuation">,</span> TRight<span class="token punctuation">?</span><span class="token punctuation">)</span></span> <span class="token generic-method"><span class="token function">Either</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">,</span> TRight<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">TLeft<span class="token punctuation">?</span></span> left <span class="token operator">=</span> <span class="token keyword">default</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span>left<span class="token punctuation">,</span> <span class="token keyword">default</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token punctuation">(</span>TLeft<span class="token punctuation">?</span><span class="token punctuation">,</span> TRight<span class="token punctuation">?</span><span class="token punctuation">)</span></span> <span class="token generic-method"><span class="token function">Either</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">,</span> TRight<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">TRight<span class="token punctuation">?</span></span> right <span class="token operator">=</span> <span class="token keyword">default</span> <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">(</span><span class="token keyword">default</span><span class="token punctuation">,</span> right<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>and use it as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">TextFormatter<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span><span class="token generic-method"><span class="token function">Either</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>DateTimeOffset<span class="token punctuation">,</span> <span class="token keyword">long</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>DateTimeOffset<span class="token punctuation">.</span>Now<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TextFormatter<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span><span class="token generic-method"><span class="token function">Either</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>DateTimeOffset<span class="token punctuation">,</span> <span class="token keyword">long</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>milliseconds<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>A bit better, but still not great. Let’s add some more helpers:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EitherExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token punctuation">(</span>TLeft<span class="token punctuation">?</span> Left<span class="token punctuation">,</span> TRight<span class="token punctuation">?</span> Right<span class="token punctuation">)</span></span> <span class="token generic-method"><span class="token function">AssertAnyDefined</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">,</span> TRight<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token punctuation">(</span><span class="token class-name">TLeft<span class="token punctuation">?</span></span> Left<span class="token punctuation">,</span> <span class="token class-name">TRight<span class="token punctuation">?</span></span> Right<span class="token punctuation">)</span> <span class="token keyword">value</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">.</span>Left <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Right <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"One of values needs to be set"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">value</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">TMapped</span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">,</span> TRight<span class="token punctuation">,</span> TMapped<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token punctuation">(</span><span class="token class-name">TLeft<span class="token punctuation">?</span></span> Left<span class="token punctuation">,</span> <span class="token class-name">TRight<span class="token punctuation">?</span></span> Right<span class="token punctuation">)</span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">,</span> TMapped<span class="token punctuation">></span></span> mapLeft<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TRight<span class="token punctuation">,</span> TMapped<span class="token punctuation">></span></span> mapRight <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TLeft</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">struct</span></span> <span class="token keyword">where</span> <span class="token class-name">TRight</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">struct</span></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>left<span class="token punctuation">,</span> right<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">AssertAnyDefined</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>left<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">mapLeft</span><span class="token punctuation">(</span>left<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>right<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">mapRight</span><span class="token punctuation">(</span>right<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"That should never happen!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><em>AssertAnyDefined</em> will ensure that at least one of the values is defined.</p> <p><em>Map</em> takes two transformation functions for each type in the tuple. It calls one of them for the defined value and returns the mapped value. Thanks to that, we’re getting simplified syntax with compiler checks for pattern matching.</p> <p>Then our function can look like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TextFormatter</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">Format</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name">DateTimeOffset<span class="token punctuation">?</span></span> DateTime<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">long</span><span class="token punctuation">?</span></span> Milliseconds<span class="token punctuation">)</span> dateTime<span class="token punctuation">)</span> <span class="token operator">=></span> dateTime<span class="token punctuation">.</span><span class="token function">Map</span><span class="token punctuation">(</span> date <span class="token operator">=></span> date<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> milliseconds <span class="token operator">=></span> DateTimeOffset<span class="token punctuation">.</span><span class="token function">FromUnixTimeMilliseconds</span><span class="token punctuation">(</span>milliseconds<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s not perfect, as we cannot use the native language capabilities, but with a few helper methods, we could provide some mapping code without building byzantine structures. Yet, tuple syntax is still pretty verbose and fragile.</p> <p><strong>What if we need to provide the <em>null</em> value as one of the options?</strong> Our code won’t be able to distinguish the difference between present or unset values. C# language won’t help us, unfortunately. We must roll up our sleeves and define a type that will allow differentiation between set and unset states. We need to be able to say that this object may or may not have some value. Let’s do that!</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Maybe<span class="token punctuation">&lt;</span>TSomething<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">TSomething<span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsPresent <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">Maybe</span><span class="token punctuation">(</span><span class="token class-name">TSomething</span> <span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> isPresent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token keyword">value</span> <span class="token operator">=</span> <span class="token keyword">value</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>IsPresent <span class="token operator">=</span> isPresent<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">readonly</span> <span class="token class-name">Maybe<span class="token punctuation">&lt;</span>TSomething<span class="token punctuation">></span></span> Empty <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Maybe<span class="token punctuation">&lt;</span>TSomething<span class="token punctuation">></span></span> <span class="token function">Of</span><span class="token punctuation">(</span><span class="token class-name">TSomething</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">value</span> <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Maybe<span class="token punctuation">&lt;</span>TSomething<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> Empty<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">TSomething</span> <span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsPresent <span class="token punctuation">?</span> <span class="token keyword">value</span><span class="token operator">!</span> <span class="token punctuation">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">TSomething</span> <span class="token function">GetOrDefault</span><span class="token punctuation">(</span><span class="token class-name">TSomething</span> defaultValue <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">)</span> <span class="token operator">=></span> IsPresent <span class="token punctuation">?</span> <span class="token keyword">value</span> <span class="token operator">??</span> defaultValue <span class="token punctuation">:</span> defaultValue<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s a simple class that keeps the current value together with information, whether it’s present or not. It also has helper functions to provide basic processing of the value. For inspiration, check <a href="https://www.baeldung.com/java-optional">Java Optional</a>.</p> <p>How to use it in our code? We could replace the nullable fields in our tuple and use <em>IsPresent</em> to verify if the value was set. Still, if we’re already doing magic, then let’s try to do already one more step ahead.</p> <p><strong>Let’s define the class that will allow us to say that the value is either of one type or another.</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Either<span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">,</span> TRight<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Maybe<span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">></span></span> Left <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Maybe<span class="token punctuation">&lt;</span>TRight<span class="token punctuation">></span></span> Right <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">Either</span><span class="token punctuation">(</span><span class="token class-name">TLeft</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Left <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>TLeft<span class="token operator">></span><span class="token punctuation">.</span><span class="token function">Of</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Right <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>TRight<span class="token operator">></span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">Either</span><span class="token punctuation">(</span><span class="token class-name">TRight</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Left <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>TLeft<span class="token operator">></span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> Right <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>TRight<span class="token operator">></span><span class="token punctuation">.</span><span class="token function">Of</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">Either</span><span class="token punctuation">(</span><span class="token class-name">Maybe<span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">></span></span> left<span class="token punctuation">,</span> <span class="token class-name">Maybe<span class="token punctuation">&lt;</span>TRight<span class="token punctuation">></span></span> right<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>left<span class="token punctuation">.</span>IsPresent <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>right<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>right<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Left <span class="token operator">=</span> left<span class="token punctuation">;</span> Right <span class="token operator">=</span> right<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">TMapped</span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TMapped<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">,</span> TMapped<span class="token punctuation">></span></span> mapLeft<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TRight<span class="token punctuation">,</span> TMapped<span class="token punctuation">></span></span> mapRight <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Left<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">mapLeft</span><span class="token punctuation">(</span>Left<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Right<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">mapRight</span><span class="token punctuation">(</span>Right<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"That should never happen!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Switch</span><span class="token punctuation">(</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TLeft<span class="token punctuation">></span></span> onLeft<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TRight<span class="token punctuation">></span></span> onRight <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Left<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">onLeft</span><span class="token punctuation">(</span>Left<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Right<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">onRight</span><span class="token punctuation">(</span>Right<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"That should never happen!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, it has two constructors, one with the first (<em>left</em>) type instance and the other with the second (<em>right</em>) type. We’re using them to set the internal values wrapped with <em>Maybe</em> type. I also defined <em>Map</em> method (accordingly to our tuple example) plus the <em>Switch</em> method, just in case we don’t want to return anything.</p> <p>Let’s update our text formatter code:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TextFormatter</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">Format</span><span class="token punctuation">(</span><span class="token class-name">Either<span class="token punctuation">&lt;</span>DateTimeOffset<span class="token punctuation">,</span> <span class="token keyword">long</span><span class="token punctuation">></span></span> dateTime<span class="token punctuation">)</span> <span class="token operator">=></span> dateTime<span class="token punctuation">.</span><span class="token function">Map</span><span class="token punctuation">(</span> date <span class="token operator">=></span> date<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> milliseconds <span class="token operator">=></span> DateTimeOffset<span class="token punctuation">.</span><span class="token function">FromUnixTimeMilliseconds</span><span class="token punctuation">(</span>milliseconds<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>and show how to call it:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">TextFormatter<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">Either<span class="token punctuation">&lt;</span>DateTimeOffset<span class="token punctuation">,</span> <span class="token keyword">long</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>dateTime<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> TextFormatter<span class="token punctuation">.</span><span class="token function">Format</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">Either<span class="token punctuation">&lt;</span>DateTimeOffset<span class="token punctuation">,</span> <span class="token keyword">long</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>milliseconds<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Of course, we could use union types not only for the input parameters. They’re even more helpful if we’re using it as result types. See:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">FileOpeningError</span> <span class="token punctuation">{</span> FileDoesNotExist <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FileProcessor</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Either<span class="token punctuation">&lt;</span>FileStream<span class="token punctuation">,</span> FileOpeningError<span class="token punctuation">></span></span> <span class="token function">ReadFile</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> fileName<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>File<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span>fileName<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Either<span class="token punctuation">&lt;</span>FileStream<span class="token punctuation">,</span> FileOpeningError<span class="token punctuation">></span></span><span class="token punctuation">(</span>FileOpeningError<span class="token punctuation">.</span>FileDoesNotExist<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Either<span class="token punctuation">&lt;</span>FileStream<span class="token punctuation">,</span> FileOpeningError<span class="token punctuation">></span></span><span class="token punctuation">(</span>File<span class="token punctuation">.</span><span class="token function">Open</span><span class="token punctuation">(</span>fileName<span class="token punctuation">,</span> FileMode<span class="token punctuation">.</span>Open<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Thanks to that, we could run different logic if the processing was successful or failed. Doing that by throwing and catching exceptions is not always the most intuitive way. Read more in Scott Wlaschin’s article about <a href="https://fsharpforfunandprofit.com/rop/">Railway Oriented Programming</a>.</p> <p><strong>Okay, but what if our input parameter would need to be a union of three types?</strong> Then, we need to define the following class:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OneOf<span class="token punctuation">&lt;</span>T1<span class="token punctuation">,</span> T2<span class="token punctuation">,</span> T3<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Maybe<span class="token punctuation">&lt;</span>T1<span class="token punctuation">></span></span> First <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Maybe<span class="token punctuation">&lt;</span>T2<span class="token punctuation">></span></span> Second <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Maybe<span class="token punctuation">&lt;</span>T3<span class="token punctuation">></span></span> Third <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">OneOf</span><span class="token punctuation">(</span><span class="token class-name">T1</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> First <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T1<span class="token operator">></span><span class="token punctuation">.</span><span class="token function">Of</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Second <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T2<span class="token operator">></span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> Third <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T3<span class="token operator">></span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">OneOf</span><span class="token punctuation">(</span><span class="token class-name">T2</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> First <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T1<span class="token operator">></span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> Second <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T2<span class="token operator">></span><span class="token punctuation">.</span><span class="token function">Of</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Third <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T3<span class="token operator">></span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">OneOf</span><span class="token punctuation">(</span><span class="token class-name">T3</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> First <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T1<span class="token operator">></span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> Second <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T2<span class="token operator">></span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> Third <span class="token operator">=</span> Maybe<span class="token operator">&lt;</span>T3<span class="token operator">></span><span class="token punctuation">.</span><span class="token function">Of</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">TMapped</span> <span class="token generic-method"><span class="token function">Map</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TMapped<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>T1<span class="token punctuation">,</span> TMapped<span class="token punctuation">></span></span> mapT1<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>T2<span class="token punctuation">,</span> TMapped<span class="token punctuation">></span></span> mapT2<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>T3<span class="token punctuation">,</span> TMapped<span class="token punctuation">></span></span> mapT3 <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>First<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">mapT1</span><span class="token punctuation">(</span>First<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Second<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">mapT2</span><span class="token punctuation">(</span>Second<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Third<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">mapT3</span><span class="token punctuation">(</span>Third<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"That should never happen!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Switch</span><span class="token punctuation">(</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>T1<span class="token punctuation">></span></span> onT1<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>T2<span class="token punctuation">></span></span> onT2<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>T3<span class="token punctuation">></span></span> onT3 <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>First<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">onT1</span><span class="token punctuation">(</span>First<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Second<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">onT2</span><span class="token punctuation">(</span>Second<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Third<span class="token punctuation">.</span>IsPresent<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">onT3</span><span class="token punctuation">(</span>Third<span class="token punctuation">.</span><span class="token function">GetOrThrow</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Exception</span><span class="token punctuation">(</span><span class="token string">"That should never happen!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre></div> <p>What if we need more? Then we need to define more classes like that, ending with the maximum number of types we want to support in the union type. Typically our union won’t have more than a few of them.</p> <p>We could also use <a href="https://github.com/mcintyre321/OneOf">OneOf</a> library from Harry McIntyre. It already provides base classes for that, together with code generation and all that jazz.</p> <h2 id="whats-my-final-opinion" style="position:relative;"><a href="#whats-my-final-opinion" aria-label="whats my final opinion permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>What’s my final opinion?</h2> <p>Until we have native support, it won’t be great whatever we do. The options we have are verbose. Compare that to the F# definition:</p> <div class="gatsby-highlight" data-language="fsharp"><pre class="language-fsharp"><code class="language-fsharp"><span class="token keyword">type</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> ShoppingCartOpened <span class="token keyword">of</span> <span class="token punctuation">{</span><span class="token operator">|</span> shoppingCartId<span class="token punctuation">:</span> <span class="token class-name">Guid</span><span class="token punctuation">,</span> clientId <span class="token punctuation">:</span> <span class="token class-name">Guid</span><span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> ProductItemAddedToShoppingCart <span class="token keyword">of</span> <span class="token punctuation">{</span><span class="token operator">|</span> productId <span class="token punctuation">:</span> <span class="token class-name">Guid</span><span class="token punctuation">;</span> quantity <span class="token punctuation">:</span> <span class="token class-name">int</span><span class="token punctuation">;</span> unitPrice <span class="token punctuation">:</span> <span class="token class-name">decimal</span> <span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> ProductItemRemovedFromShoppingCart <span class="token keyword">of</span> <span class="token punctuation">{</span><span class="token operator">|</span> productId <span class="token punctuation">:</span> <span class="token class-name">Guid</span><span class="token punctuation">;</span> quantity <span class="token punctuation">:</span> <span class="token class-name">int</span><span class="token punctuation">;</span> unitPrice <span class="token punctuation">:</span> <span class="token class-name">decimal</span> <span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> ShoppingCartConfirmed <span class="token keyword">of</span> <span class="token punctuation">{</span><span class="token operator">|</span> confirmedAt <span class="token punctuation">:</span> <span class="token class-name">System<span class="token punctuation">.</span>DateTimeOffset</span> <span class="token operator">|</span><span class="token punctuation">}</span> <span class="token operator">|</span> ShoppingCartCanceled <span class="token keyword">of</span> <span class="token punctuation">{</span><span class="token operator">|</span> confirmedAt <span class="token punctuation">:</span> <span class="token class-name">System<span class="token punctuation">.</span>DateTimeOffset</span> <span class="token operator">|</span><span class="token punctuation">}</span></code></pre></div> <p>See also how succint is TypeScript version in my article <a href="/en/type_script_node_Js_event_sourcing">Straightforward Event Sourcing with TypeScript and NodeJS</a>.</p> <p>Nevertheless, those tactics described in the article can be helpful and may be good enough if we get used to them. They may enhance our business logic definition and trust in our type definition. Yet, we need to remember that they won’t guard us. They may also be harder to use with frameworks that don’t expect to have such type definition.</p> <p><strong>Would I use them?</strong></p> <p>Yes, but in the places where they can shine the most, like domain code and business logic. The good idea is to use it in public API to clarify the input intention. It’s worth ensuring that we’re not bikeshedding and that they bring benefits. I’d probably try to start explicitly modelling classes where you see the value and avoid complex construction replacing the native code like <em>OneOf</em>. They’re helpful, but they may make rewriting the codebase harder when we finally get union types in the language natively.</p> <p>I encourage you to play with them and see how they suit you. And most importantly <strong>putting pressure on C# language designers to finally deliver it!</strong>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to effectively compose your business logic]]>https://event-driven.io/en/how_to_effectively_compose_your_business_logic/https://event-driven.io/en/how_to_effectively_compose_your_business_logic/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/9d13107f0ccc3b9d6aa22fe137d9dc91/332ff/2022-07-20-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADD0lEQVQoz0XOS0giDwDH8bkldCyCoFMSCGkdmsZKote/aTrEEhoaYSllWjmPyiAtGnWKXsPYxDRoDylKTZlgSoviL/a6RPc6dAjsUFBesqggWBZmd9nP/cf3B4iieHx8fH5+LgiC0+lkGGZzc/Pu7i6dTj8/P2ez2c/Pzy/Jx8dHNpvNZDJPT08PDw/39/dAPB5PpVKXl5ehUGh8fHxkZCQWi2Ukb29v39/fP//6+vp6fX19f3/PZDKPj4/pdBqIRqOiKB4cHKRSqcPDQ51Ot7Gxsb+/LwjCyclJMpk8PT29urq6vb29ubm5vr5+eXn5VwZBUK1WQxBUU1NTV1en0WhaW1uHh4ebmppgGP4h0Wq1XV1dJpPJYDAQBOF0OicnJ0mSBEpKSlQqlVKpLC8v/z2AIGhpacnn83V2dur1erPZbDKZLBaLzWYzGo2Dg4MYhhEE4XA4gIaGhvr6erVaXVVVBYIgDMO1tbU2my2RSGxvb6MoajAYrFYriqKjo6N2u93j8Xi9Xo/HQ1EUUFxcrFAolErl735jYyOCIARBiKJ4dHQUCoV6e3vb2tqsVqvFYuno6HC5XG63+89tuVxeVlYGQRAIghUVFdXV1TAM+3y+RCIhiqLf719eXsYwzGw2a7XaWolOp+vp6enr6wNKS0srKyv/k2g0Gr1eT9P0toRlWQRBYrFYOBwOBoMMw5AkabfbBwYG+vv7MQwDEAQxGo0EQYyNjU1MTCxJWJblJDiOsyy7uroaiUTi8Xgymby4uPhfcnZ2BhiNRhRFXS4XSZIzMzNzc3M0Tc9JOI7jeZ7juEAgEAwGI5FIOBze29uLRqO7u7uCIAAKhQIEQQRBTCYTQRBTU1OzEo/HQ9M0x3Hr6+s7OztbW1tra2s8z6+srAQCAYqi2tvbgZycHJlMlpeXB4Lg0NAQSZI0TTMMQ1HU/Py8T+L3+3mep2l6YWHB7XbjON7c3CyXywGZTJabm5ufn19QUKBSqbq7u71e7+zsLCWhaXp6etrr9S4uLpIkieO4w+FoaWkpKioqLCz8BcA1pVRS4GeEAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/9d13107f0ccc3b9d6aa22fe137d9dc91/a331c/2022-07-20-cover.png" srcset="/static/9d13107f0ccc3b9d6aa22fe137d9dc91/36ca5/2022-07-20-cover.png 200w, /static/9d13107f0ccc3b9d6aa22fe137d9dc91/a3397/2022-07-20-cover.png 400w, /static/9d13107f0ccc3b9d6aa22fe137d9dc91/a331c/2022-07-20-cover.png 800w, /static/9d13107f0ccc3b9d6aa22fe137d9dc91/332ff/2022-07-20-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Let’s say that we’d like to implement Shopping Cart. We have the following requirements:</p> <ol> <li>The customer may only add a product to the shopping cart after opening it.</li> <li>When selecting and adding a product to the basket customer needs to provide the quantity chosen. The system calculates the product price based on the current price list.</li> <li>The customer may remove a product with a given price from the cart.</li> <li>The customer can confirm the shopping cart and start the order fulfilment process.</li> <li>The customer may cancel the shopping cart and reject all selected products.</li> <li>After shopping cart confirmation or cancellation, the product can no longer be added or removed from the cart.</li> </ol> <p>A single good picture tells more than a thousand words, right? There you have it.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5b1cda5e4b9051f6398977592a2c07d1/989a8/2022-07-20-storming.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 67.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACSUlEQVQ4y32Tz2sTQRTH9+/x6NmbN0HwIHjzKF4Fr71UPImihyJixKoVQ21UqCCiYFusbailhmotpaRtuskm2ST7K9nt7s7MR3aTTRNb/cKXmZ198+V933ujKaVI0F8VUikcX2D3BFHkAB2UX0eYZYRjIIRIGcdxyiiKUmY6WrLJmAiGQvFqxeDJQo19fRHHyhOU7tK4dx63cCON6UONkGFSqSCpYP8wFDBbbDC9ZLBXXaZl5nE37tN4cAHnzU2UlKmL9FoqAkoKlIz7gvwFqcBoexidHn7QxXWb+JaBV/lF2D4cieyCKIOsjd3X8isGqzstnPkJzJnrSOPniKWT0DsBm3qI6ezTqT/Fa80hdj4Rb75DuXW024XfzBcPaD2+zMGts8j9b6mUTGwM6yvT7wQfS00mX2/zdWubhl6gffgW+9FFjMkzyPIS2nrZpmJ2cX+8x12bRXqNYYHlgJlogrXdNjMLFbb1Xczqc5z6HPb8BNXcVSK9lNRQjfXseITGkR3FQJQ4EDWa1Rk86wNKRIRHAVJKNKkGAomlgc0EQiqqVoRuxYRhB6ijehXEQRFqJUTcxbX3OArq400ZncPjeQQ/UuS+VHj4WadiLNI2pwm2pmjeOYebuwQyHpu/jNpJa33BIFYUvpu8XG6g14vY7QK9nRdYuSu4+WsgwsEcyrRYauD0n4LJ6nZ9LKeHZ69zWH6GZ23gOxae3RoWVf21aqfNWvYMM0TBKk19iihYP45Bndq8/2aYMnmORzXMRhG/pw/+yRMZZoJ/AM8ZJYRsp5I9AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="event storming" title="event storming" src="/static/5b1cda5e4b9051f6398977592a2c07d1/a331c/2022-07-20-storming.png" srcset="/static/5b1cda5e4b9051f6398977592a2c07d1/36ca5/2022-07-20-storming.png 200w, /static/5b1cda5e4b9051f6398977592a2c07d1/a3397/2022-07-20-storming.png 400w, /static/5b1cda5e4b9051f6398977592a2c07d1/a331c/2022-07-20-storming.png 800w, /static/5b1cda5e4b9051f6398977592a2c07d1/989a8/2022-07-20-storming.png 968w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We may already notice that our shopping cart is a simple state machine. It’s either pending, and we can add products to it, or closed (confirmed or cancelled). If we were object-oriented Java developers, we could model our shopping cart as:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">ShoppingCartStatus</span> status<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">List</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">PricedProductItem</span><span class="token punctuation">></span></span> productItems<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">OffsetDateTime</span> confirmedAt<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">OffsetDateTime</span> canceledAt<span class="token punctuation">;</span> <span class="token comment">// (...) horde of the public getters and setters</span> <span class="token punctuation">}</span></code></pre></div> <p>This could be good enough for many scenarios, yet <strong>it doesn’t tell us much about the expected behaviour</strong>. We won’t know if and when confirmation and cancellation dates are set. Our object structure will allow us to change anytime we want. Even set null. Of course, we could add more validation logic in the constructor, etc. Yet it would make our code only more blurry. Wouldn’t it be nice if the compiler helped us to catch our mistakes? It’d be great if we could express the state machine with code and model the state transitions explicitly, right?</p> <p>Luckily, Java from version 17 can help us. How? Let’s start with <a href="https://openjdk.org/jeps/395">records</a>. They are shortened syntax for classes simplifying their definition and making their instances immutable. Immutability is a topic for its own article. For now, let’s focus on what we want to get from them. We’d like to make our code predictable by knowing precisely when we’re doing state transition. By that, we can restrict unexpected modifications. We know that from the place we build our state, we may trust it and don’t need to validate it each time we use it. That reduces the number of IFs and required unit tests.</p> <p>For example, we could define <a href="">ETag</a> value object to restrict the proper format:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ETag</span><span class="token punctuation">(</span><span class="token class-name">String</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">Pattern</span> <span class="token class-name">ETagPattern</span> <span class="token operator">=</span> <span class="token class-name">Pattern</span><span class="token punctuation">.</span><span class="token function">compile</span><span class="token punctuation">(</span><span class="token string">"\"([^\"]*)\""</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token annotation punctuation">@JsonCreator</span> <span class="token keyword">public</span> <span class="token class-name">ETag</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> regexMatcher <span class="token operator">=</span> <span class="token class-name">ETagPattern</span><span class="token punctuation">.</span><span class="token function">matcher</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>regexMatcher<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token string">"Not an ETag header"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ETag</span> <span class="token function">weak</span><span class="token punctuation">(</span><span class="token class-name">Object</span> value<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ETag</span><span class="token punctuation">(</span><span class="token string">"W/\"%s\""</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>value<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">boolean</span> <span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> value <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">long</span> <span class="token function">toLong</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>value <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Header is empty"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">var</span> regexMatcher <span class="token operator">=</span> <span class="token class-name">ETagPattern</span><span class="token punctuation">.</span><span class="token function">matcher</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> regexMatcher<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token class-name">Long</span><span class="token punctuation">.</span><span class="token function">parseLong</span><span class="token punctuation">(</span>regexMatcher<span class="token punctuation">.</span><span class="token function">group</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Coming back to our shopping cart, we could also make our entity a record. Yet, that wouldn’t be enough fun and help for us. Predictability is excellent, but we’d like to model our state machine explicitly. <strong>We can use another new concept, so <a href="https://openjdk.org/jeps/409">sealed interface</a>.</strong> They allow restricting the set of implementations for our interfaces. Check what we can do with them:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">sealed</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">EmptyShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">PendingShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> id<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> productItems <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ConfirmedShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> id<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> productItems<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> confirmedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">CanceledShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> id<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> productItems<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> canceledAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>At first glance, the syntax may look weird, but you can get used to it. When we do, then by looking at the code, we’ll know that an instance of a <strong>shopping cart will be either empty (not initialised), pending, confirmed or cancelled.</strong> This is powerful, as it’s expressive, plus sealed interface won’t allow anyone to do cowboy implementation of our interface. Plus, it works great with new <a href="https://openjdk.org/jeps/406">Java pattern matching capabilities</a>. We’ll get to that later.</p> <p>Sealed interfaces are the same concept as <a href="https://docs.scala-lang.org/tour/traits.html">traits in Scala</a> or similar to <a href="https://kotlinlang.org/docs/sealed-classes.html">sealed classes in Kotlin</a> and <a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types">Union Types in TypeScript</a>. If you’re searching for a rabbit hole, check <a href="https://en.wikipedia.org/wiki/Algebraic_data_type">algebraic types</a>, it might appear that your favourite language also supports them.</p> <p>Using sealed interfaces, we could also define a set of commands to know precisely what could happen with our shopping cart.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCartCommand</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">OpenShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartCommand</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">AddProductItemToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItem <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartCommand</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">RemoveProductItemFromShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> productItem <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartCommand</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ConfirmShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartCommand</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">CancelShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartCommand</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Now we have the code telling us precisely what business state we may expect and all the operations we can do on the shopping cart. Now, what we have left is to define command handling.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ProductItemAddedToShoppingCart</span> <span class="token function">addProductItem</span><span class="token punctuation">(</span> <span class="token class-name">ProductPriceCalculator</span> productPriceCalculator<span class="token punctuation">,</span> <span class="token class-name">AddProductItemToShoppingCart</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> shoppingCart <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>shoppingCart <span class="token keyword">instanceof</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>PendingShoppingCart</span> pendingShoppingCart<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Removing product item for cart in '%s' status is not allowed."</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> pricedProductItem <span class="token operator">=</span> productPriceCalculator<span class="token punctuation">.</span><span class="token function">calculate</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>pricedProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> command<span class="token punctuation">.</span><span class="token function">shoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pricedProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span> <span class="token function">removeProductItem</span><span class="token punctuation">(</span> <span class="token class-name">RemoveProductItemFromShoppingCart</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> shoppingCart <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>shoppingCart <span class="token keyword">instanceof</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>PendingShoppingCart</span> pendingShoppingCart<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Removing product item for cart in '%s' status is not allowed."</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">hasEnough</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span><span class="token punctuation">(</span> command<span class="token punctuation">.</span><span class="token function">shoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> command<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCartConfirmed</span> <span class="token function">confirm</span><span class="token punctuation">(</span><span class="token class-name">ConfirmShoppingCart</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>shoppingCart <span class="token keyword">instanceof</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>PendingShoppingCart</span> pendingShoppingCart<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Removing product item for cart in '%s' status is not allowed."</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span><span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCartCanceled</span> <span class="token function">cancel</span><span class="token punctuation">(</span><span class="token class-name">CancelShoppingCart</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> shoppingCart<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>shoppingCart <span class="token keyword">instanceof</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>PendingShoppingCart</span> pendingShoppingCart<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Removing product item for cart in '%s' status is not allowed."</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span><span class="token function">getClass</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">getName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span><span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, there’s nothing spectacular happening here. We have a set of functions: taking command and current state, processing the business logic and returning the event as a result. <strong>Oh wait, did someone mention <em>event</em>?</strong> I wouldn’t be myself if I didn’t add a sprinkle of Event Sourcing in my samples. It matches well with the approach we took. We’re focusing on modelling business logic and exposing it directly through the code. Event Sourcing can help in knowing the intent and registering the results of our business logic. Events here are facts and the checkpoints for our business workflow. Of course, we could return the next state instead, but hey, we’re on the blog registered under the <em>event-driven.io</em> domain!</p> <p><strong>Let’s define events that can be registered for the shopping cart:</strong></p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> productItem <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> productItem <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> confirmedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartCanceled</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> canceledAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCartEvent</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>In Event Sourcing, the application state is stored in events. Classical entities are represented as series of events called <em>streams</em>. To get the current state, we need to: Get all events for a given stream. We choose them based on the stream identifier (derived from the business object/record id). An event store retains the events for a given stream in the order they were appended; retrieval should preserve the order. Create a default, empty entity. Apply each event sequentially to the entity. We’re taking the current state and evolving it into the new one for each event. Method transforming the state based on the event is usually called <em>when</em>, <em>apply</em> or <em>evolve</em>. Read more in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a>.</p> <p>Let’s define it for our case:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">sealed</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token comment">// (...)</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> <span class="token function">evolve</span><span class="token punctuation">(</span> <span class="token class-name">ShoppingCart</span> state<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartEvent</span> event <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartEvent<span class="token punctuation">.</span>ShoppingCartOpened</span> shoppingCartOpened<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>state <span class="token keyword">instanceof</span> <span class="token class-name">EmptyShoppingCart</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">yield</span> state<span class="token punctuation">;</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">PendingShoppingCart</span><span class="token punctuation">(</span> shoppingCartOpened<span class="token punctuation">.</span><span class="token function">shoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shoppingCartOpened<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItems</span><span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartEvent<span class="token punctuation">.</span>ProductItemAddedToShoppingCart</span> productItemAddedToShoppingCart<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>state <span class="token keyword">instanceof</span> <span class="token class-name">PendingShoppingCart</span> pendingShoppingCart<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">yield</span> state<span class="token punctuation">;</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>PendingShoppingCart</span><span class="token punctuation">(</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>productItemAddedToShoppingCart<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartEvent<span class="token punctuation">.</span>ProductItemRemovedFromShoppingCart</span> productItemRemovedFromShoppingCart<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>state <span class="token keyword">instanceof</span> <span class="token class-name">PendingShoppingCart</span> pendingShoppingCart<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">yield</span> state<span class="token punctuation">;</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>PendingShoppingCart</span><span class="token punctuation">(</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>productItemRemovedFromShoppingCart<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartEvent<span class="token punctuation">.</span>ShoppingCartConfirmed</span> shoppingCartConfirmed<span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>state <span class="token keyword">instanceof</span> <span class="token class-name">PendingShoppingCart</span> pendingShoppingCart<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">yield</span> state<span class="token punctuation">;</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>ConfirmedShoppingCart</span><span class="token punctuation">(</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shoppingCartConfirmed<span class="token punctuation">.</span><span class="token function">confirmedAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartEvent<span class="token punctuation">.</span>ShoppingCartCanceled</span> shoppingCartCanceled<span class="token operator">:</span><span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>state <span class="token keyword">instanceof</span> <span class="token class-name">PendingShoppingCart</span> pendingShoppingCart<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">yield</span> state<span class="token punctuation">;</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>ConfirmedShoppingCart</span><span class="token punctuation">(</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pendingShoppingCart<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shoppingCartCanceled<span class="token punctuation">.</span><span class="token function">canceledAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Yup, it can also be a part of our Shopping Cart definition. Thanks to that, we can see in one place all the possible states and how to get to them from the sequence of events. This is also a fantastic example of self-documenting code being the source of truth both for the business process and implementation. We’re also using here <a href="https://openjdk.org/jeps/406">mentioned new pattern matching</a>. They’re super cool, as the compiler will guard us and fail if we forget to handle the newly added event in a dedicated switch branch.</p> <p>The essential thing to note is that we can use <em>instanceof</em> to ensure that our current state is as expected. We should not throw exceptions here, as our business logic should check all invariants. If we threw an exception, then we wouldn’t give ourselves a chance to correct bugs in our stream. Read more in <a href="/en/should_you_throw_exception_when_rebuilding_state_from_events/">Should you throw an exception when rebuilding the state from events?</a>.</p> <p><strong>Now, let’s wrap it up and go to Grand Finale!</strong></p> <p>We could group our command handling into the method called <em>decide</em>:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCartEvent</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token function">decide</span><span class="token punctuation">(</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ProductPriceCalculator</span><span class="token punctuation">></span></span> getProductPriceCalculator<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartCommand</span> command<span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span> state <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCartEvent</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>command<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">OpenShoppingCart</span> openCommand <span class="token operator">-></span> <span class="token keyword">open</span><span class="token punctuation">(</span>openCommand<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">AddProductItemToShoppingCart</span> addCommand <span class="token operator">-></span> <span class="token function">addProductItem</span><span class="token punctuation">(</span>getProductPriceCalculator<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> addCommand<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">RemoveProductItemFromShoppingCart</span> removeProductCommand <span class="token operator">-></span> <span class="token function">removeProductItem</span><span class="token punctuation">(</span>removeProductCommand<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ConfirmShoppingCart</span> confirmCommand <span class="token operator">-></span> <span class="token function">confirm</span><span class="token punctuation">(</span>confirmCommand<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">CancelShoppingCart</span> cancelCommand <span class="token operator">-></span> <span class="token function">cancel</span><span class="token punctuation">(</span>cancelCommand<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>This method takes the command and current state and runs the business logic. <strong>It’s a decent example of how using just <a href="https://en.wikipedia.org/wiki/Pure_function">pure functions</a> enables composability.</strong> We have better encapsulation, a single entry point for our business logic. We can model union types, pass a specific parameter thanks to the sealed interfaces, and then have strong typing and trust in our objects. Plus, we’re getting a self-documenting code, easy to test. Immutability only strengthens that.</p> <p>We could generalise all of that and define the following type:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Decider</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span><span class="token punctuation">(</span> <span class="token class-name">BiFunction</span><span class="token operator">&lt;</span><span class="token class-name">Command</span><span class="token punctuation">,</span> <span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span> decide<span class="token punctuation">,</span> <span class="token class-name">BiFunction</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">,</span> <span class="token class-name">State</span><span class="token punctuation">></span></span> evolve<span class="token punctuation">,</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">></span></span> getInitialState <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span></code></pre></div> <p>It groups business logic (<em>decide</em> function), state evolution and rebuild (<em>evolve</em>) together with the initial state. It’s similar to the <a href="https://www.dddcommunity.org/wp-content/uploads/files/pdf_articles/Vernon_2011_1.pdf">Aggregate pattern</a> in a way as it represents a particular decision process, ensuring that all the business invariants are checked, and guards consistency. It’s based on the functional composition, which speaks to me, as it helps me to focus on the specific business process instead of how to glue the staff together.</p> <p>For our shopping cart this would look as follows:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartService</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token class-name">Decider</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ShoppingCart</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartCommand</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartEvent</span><span class="token punctuation">></span></span> <span class="token function">shoppingCartDecider</span><span class="token punctuation">(</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ProductPriceCalculator</span><span class="token punctuation">></span></span> getProductPriceCalculator <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Decider</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span> <span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token class-name">ShoppingCartService</span><span class="token punctuation">.</span><span class="token function">decide</span><span class="token punctuation">(</span>getProductPriceCalculator<span class="token punctuation">,</span> command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCart</span><span class="token operator">::</span><span class="token function">evolve</span><span class="token punctuation">,</span> <span class="token class-name">EmptyShoppingCart</span><span class="token operator">::</span><span class="token keyword">new</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We can define the general Command Handler as:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CommandHandler</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">EventStore</span> eventStore<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Decider</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> decider<span class="token punctuation">;</span> <span class="token class-name">CommandHandler</span><span class="token punctuation">(</span> <span class="token class-name">EventStore</span> eventStore<span class="token punctuation">,</span> <span class="token class-name">Decider</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">State</span><span class="token punctuation">,</span> <span class="token class-name">Command</span><span class="token punctuation">,</span> <span class="token class-name">Event</span><span class="token punctuation">></span></span> decider <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventStore <span class="token operator">=</span> eventStore<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>decider <span class="token operator">=</span> decider<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token class-name">AppendResult</span> <span class="token function">handle</span><span class="token punctuation">(</span> <span class="token class-name">String</span> streamId<span class="token punctuation">,</span> <span class="token class-name">Command</span> command<span class="token punctuation">,</span> <span class="token class-name">ETag</span> eTag <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> events <span class="token operator">=</span> eventStore<span class="token punctuation">.</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Event</span><span class="token punctuation">></span></span><span class="token function">read</span><span class="token punctuation">(</span>streamId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> state <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">collect</span><span class="token punctuation">(</span><span class="token function">foldLeft</span><span class="token punctuation">(</span>decider<span class="token punctuation">.</span><span class="token function">getInitialState</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> decider<span class="token punctuation">.</span><span class="token function">evolve</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> newEvents <span class="token operator">=</span> decider<span class="token punctuation">.</span><span class="token function">decide</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> state<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> eventStore<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span> streamId<span class="token punctuation">,</span> eTag<span class="token punctuation">,</span> <span class="token class-name">Arrays</span><span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span>newEvents<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">iterator</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Such handler can be used in the regular API controller as:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token annotation punctuation">@RestController</span> <span class="token annotation punctuation">@RequestMapping</span><span class="token punctuation">(</span><span class="token string">"api/shopping-carts"</span><span class="token punctuation">)</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartsController</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">CommandHandler</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ShoppingCart</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartCommand</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartEvent</span><span class="token punctuation">></span></span> commandHandler<span class="token punctuation">;</span> <span class="token class-name">ShoppingCartsController</span><span class="token punctuation">(</span> <span class="token class-name">CommandHandler</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ShoppingCart</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartCommand</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartEvent</span><span class="token punctuation">></span></span> commandHandler <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandHandler <span class="token operator">=</span> commandHandler<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@PostMapping</span><span class="token punctuation">(</span><span class="token string">"{id}/products"</span><span class="token punctuation">)</span> <span class="token class-name">ResponseEntity</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Void</span><span class="token punctuation">></span></span> <span class="token function">addProduct</span><span class="token punctuation">(</span> <span class="token annotation punctuation">@PathVariable</span> <span class="token class-name">UUID</span> id<span class="token punctuation">,</span> <span class="token annotation punctuation">@RequestBody</span> <span class="token class-name">ShoppingCartsRequests<span class="token punctuation">.</span>AddProduct</span> request<span class="token punctuation">,</span> <span class="token annotation punctuation">@RequestHeader</span><span class="token punctuation">(</span>name <span class="token operator">=</span> <span class="token class-name">HttpHeaders</span><span class="token punctuation">.</span>IF_MATCH<span class="token punctuation">)</span> <span class="token annotation punctuation">@Parameter</span><span class="token punctuation">(</span>in <span class="token operator">=</span> <span class="token class-name">ParameterIn</span><span class="token punctuation">.</span>HEADER<span class="token punctuation">,</span> required <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">,</span> schema <span class="token operator">=</span> <span class="token annotation punctuation">@Schema</span><span class="token punctuation">(</span>type <span class="token operator">=</span> <span class="token string">"string"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token annotation punctuation">@NotNull</span> <span class="token class-name">ETag</span> ifMatch <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>request<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token string">"Product Item has to be defined"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> result <span class="token operator">=</span> commandHandler<span class="token punctuation">.</span><span class="token function">handle</span><span class="token punctuation">(</span> <span class="token string">"shopping_cart-%s"</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">AddProductItemToShoppingCart</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">ProductItem</span><span class="token punctuation">(</span> request<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">productId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> request<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">quantity</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> ifMatch <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>result <span class="token keyword">instanceof</span> <span class="token class-name">Success</span> success<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token class-name">ResponseEntity</span><span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token class-name">HttpStatus</span><span class="token punctuation">.</span>PRECONDITION_FAILED<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token class-name">ResponseEntity</span> <span class="token punctuation">.</span><span class="token function">ok</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">eTag</span><span class="token punctuation">(</span>success<span class="token punctuation">.</span><span class="token function">nextExpectedRevision</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">value</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p>The rest of handlers will use exactly the same pattern.</p> <p>Check the full sample in my repo: <a href="https://github.com/oskardudycz/EventSourcing.JVM/pull/34">https://github.com/oskardudycz/EventSourcing.JVM/pull/34</a>.</p> <p>And now, here’s the deal. I left that as the last point. Last but not least: <strong>All KUDOS go to <a href="https://twitter.com/thinkb4coding">Jérémie Chassaing</a>.</strong> He coined Decider pattern and described it in detail. I encourage you to check his:</p> <ul> <li><a href="https://thinkbeforecoding.com/post/2021/12/17/functional-event-sourcing-decider">article</a>,</li> <li><a href="https://www.youtube.com/watch?v=whFfzQfdJZg">talk</a>,</li> <li><a href="https://gist.github.com/thinkbeforecoding/026a1d90ea2f3ea86d151b1229cad932">detailed example with a walkthrough in F#</a>.</li> </ul> <p>That should give you an even better understanding of this pattern than my humble article. I touched on one possible implementation path, but there’s more. You can use it not only for Event Sourcing but also for regular business logic processing.</p> <p>Thanks also goes to <a href="https://twitter.com/rbartelink">Ruben Bartelink</a> for the numerous discussions and patiently explaining me details of those concepts.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If you want to see C# version check <a href="/en/union_types_in_csharp">Union types in C#</a> or the more succing <a href="/en/type_script_node_Js_event_sourcing">TypeScript</a>.</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Event-driven distributed processes by example]]>https://event-driven.io/en/event_driven_distributed_processes_by_example/https://event-driven.io/en/event_driven_distributed_processes_by_example/<p>Some time ago, I wrote the general rules on processing the event-driven services. check my article <a href="/en/saga_process_manager_distributed_transactions">Saga and Process Manager - distributed processes in practice</a>. It explained the main differences between process managers, saga and choreography. Today I’d like to expand on how to tackle them using Event Sourcing and event stores.</p> <p>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 <em>microtransactions</em>. Each operation is handled by the module that’s the source of truth and can make autonomous decisions. The distributed process is triggered by the event registered and published in the system, e.g. shopping cart confirmed. Then another module can subscribe to it and take it from there. It knows what should be the next operation, e.g. initiating the order process. It sends a command that is handled, and business logic creates another event. This event is the trigger for the next step of the workflow. Such <em>lasagne</em> of event/command/event/command continues until the process is finished (with success or failure).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/7fc6987a2f4087964db9d2440e1a43b1/38f79/2022-07-13-lasagne.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAACZUlEQVQozy2RPWgTYQCGD3R2dRURwdWhuHQTdLOCg2Od6g+KIAhdxS4dRHEQdRIrocYW82N+mqYmaZqmaZpr/ntNLpdL7i733fd3933XS4ItaFrhXV+eh/cVCPMI83o6DEXiYrlWrh3ki2KtebhbqgR/xferzYbUFivNWrMltdVSuREMx4LhqAEwtj0BERfZXkNSvq8Gl3z+cGTNvxL45vNH1zbi66lILBEIRVd/BgPhWL64v5HOLfn8vuUVzYDY8YSBZQPMED2ifKQD2h8gzcR93er2Tc2kXZ3IPVPRLFnROqqh9AYmtCkfA8wszARIXUhdEzkmtCF1LcwA5tAZIjaWpGK3GEEm0FXVskeQepBwSDnAHKDTMsBMVo2t/N52QcztlFqKjm0OVaWaX333/vaXN3f6gYX250fK8jy0tAHkEwxyEHUBYgJ1vLPNzgKxA4oxUvihhhbL/vnSxwfdr0/lt/cqT65ohVC+LCWSvzdzhXQ23zzsCuapwESecMseg95hem4q+3hanLvRen2rOnt5//n17P2LsZmrXTG/V2+HI2vJ1FY8kVL6pgAw1wak1em3FU3pA5vxzOKzT9MXQlPndm6erz68VH95rbgwI5er0D2Ru5pYaVQbrVqzbSJHsDCvH3Ti6+nERmYzV+gZmLBRPZMMv5jdezWl+u62oh8sYkM2NiG1sIsdDztD4ngW4RPt3VI1mcqmNrczWzuqBizC8dExdI91uWF0ihY7AZifPsoBZpPBMAPQBsgRJINUJhqtTs+QVQM7Q2QfYZtj26XuH8KPqeNS5lE+pOx/CBsi6v4j/wUXFh/4VEmYYwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="lasagne" title="lasagne" src="/static/7fc6987a2f4087964db9d2440e1a43b1/a331c/2022-07-13-lasagne.png" srcset="/static/7fc6987a2f4087964db9d2440e1a43b1/36ca5/2022-07-13-lasagne.png 200w, /static/7fc6987a2f4087964db9d2440e1a43b1/a3397/2022-07-13-lasagne.png 400w, /static/7fc6987a2f4087964db9d2440e1a43b1/a331c/2022-07-13-lasagne.png 800w, /static/7fc6987a2f4087964db9d2440e1a43b1/8537d/2022-07-13-lasagne.png 1200w, /static/7fc6987a2f4087964db9d2440e1a43b1/38f79/2022-07-13-lasagne.png 1446w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <h2 id="batch-operations" style="position:relative;"><a href="#batch-operations" aria-label="batch operations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Batch operations</h2> <p>A common example of the distributed process is handling batch operations—for instance, group guests checkout in the hotel. So someone selects the set of guest accounts and clicks “check out”. After that, the process tries to check out all of them. If the whole process fails, there’s no compensation, but you could rerun the checkout after resolving the issues.</p> <p>The Group checkout saga can look as:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GroupCheckoutSaga</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">CommandBus</span> commandBus<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token class-name">GroupCheckoutSaga</span><span class="token punctuation">(</span><span class="token class-name">CommandBus</span> commandBus<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandBus <span class="token operator">=</span> commandBus<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">GroupCheckoutInitiated</span> groupCheckoutInitiated<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> guestAccountId <span class="token operator">:</span> groupCheckoutInitiated<span class="token punctuation">.</span><span class="token function">guestStayAccountIds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">CheckoutGuestAccount</span><span class="token punctuation">(</span>guestAccountId<span class="token punctuation">,</span> groupCheckoutInitiated<span class="token punctuation">.</span><span class="token function">groupCheckoutId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">RecordGuestStayInitiation</span><span class="token punctuation">(</span>groupCheckoutInitiated<span class="token punctuation">.</span><span class="token function">groupCheckoutId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> groupCheckoutInitiated<span class="token punctuation">.</span><span class="token function">guestStayAccountIds</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">GuestStayAccountEvent<span class="token punctuation">.</span>GuestAccountCheckoutCompleted</span> guestCheckoutCompleted<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>guestCheckoutCompleted<span class="token punctuation">.</span><span class="token function">groupCheckoutId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">RecordGuestCheckoutCompletion</span><span class="token punctuation">(</span> guestCheckoutCompleted<span class="token punctuation">.</span><span class="token function">groupCheckoutId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> guestCheckoutCompleted<span class="token punctuation">.</span><span class="token function">guestStayAccountId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> guestCheckoutCompleted<span class="token punctuation">.</span><span class="token function">completedAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">GuestStayAccountEvent<span class="token punctuation">.</span>GuestAccountCheckoutFailed</span> guestCheckoutFailed<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>guestCheckoutFailed<span class="token punctuation">.</span><span class="token function">groupCheckoutId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">RecordGuestCheckoutFailure</span><span class="token punctuation">(</span> guestCheckoutFailed<span class="token punctuation">.</span><span class="token function">groupCheckoutId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> guestCheckoutFailed<span class="token punctuation">.</span><span class="token function">guestStayAccountId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> guestCheckoutFailed<span class="token punctuation">.</span><span class="token function">failedAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>See more in <a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/hotelmanagement/groupcheckout/GroupCheckoutSaga.java">GroupCheckoutSaga.java</a></p> <p>Saga, sens commands through command bus, storing commands durable and using outbox pattern to ensure that they’re delivered. The example command bus using ESDB and its subscriptions can look like this:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ESDBCommandBus</span> <span class="token keyword">implements</span> <span class="token class-name">CommandBus</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token keyword">final</span> <span class="token class-name">String</span> commandStreamId <span class="token operator">=</span> <span class="token string">"_commands-all"</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">EventStoreDBClient</span> eventStoreDBClient<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">EventStore</span> eventStore<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">RetryPolicy</span> retryPolicy<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">></span></span> currentCorrelationId<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">></span></span> currentCausationId<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token class-name">ESDBCommandBus</span><span class="token punctuation">(</span> <span class="token class-name">EventStoreDBClient</span> eventStoreDBClient<span class="token punctuation">,</span> <span class="token class-name">EventStore</span> eventStore<span class="token punctuation">,</span> <span class="token class-name">RetryPolicy</span> retryPolicy<span class="token punctuation">,</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">></span></span> currentCorrelationId<span class="token punctuation">,</span> <span class="token class-name">Supplier</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">String</span><span class="token punctuation">></span></span> currentCausationId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventStoreDBClient <span class="token operator">=</span> eventStoreDBClient<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventStore <span class="token operator">=</span> eventStore<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>retryPolicy <span class="token operator">=</span> retryPolicy<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>currentCorrelationId <span class="token operator">=</span> currentCorrelationId<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>currentCausationId <span class="token operator">=</span> currentCausationId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">Command</span><span class="token punctuation">></span></span> <span class="token class-name">EventStore<span class="token punctuation">.</span>AppendResult</span> <span class="token function">send</span><span class="token punctuation">(</span><span class="token class-name">Command</span> command<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> retryPolicy<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>ack <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> result <span class="token operator">=</span> eventStore<span class="token punctuation">.</span><span class="token function">append</span><span class="token punctuation">(</span> commandStreamId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">CommandEnvelope</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name">CommandMetadata</span><span class="token punctuation">(</span>currentCorrelationId<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> currentCausationId<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token punctuation">(</span>result <span class="token keyword">instanceof</span> <span class="token class-name">EventStore<span class="token punctuation">.</span>AppendResult<span class="token punctuation">.</span>UnexpectedFailure</span><span class="token punctuation">)</span><span class="token punctuation">)</span> ack<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Override</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token class-name">Consumer</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">CommandEnvelope</span><span class="token punctuation">&lt;</span><span class="token class-name">Object</span><span class="token punctuation">></span><span class="token punctuation">></span></span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span> handlers<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">subscribeToStream</span><span class="token punctuation">(</span>eventStoreDBClient<span class="token punctuation">,</span> commandStreamId<span class="token punctuation">,</span> <span class="token punctuation">(</span>subscription<span class="token punctuation">,</span> resolvedEvent<span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> commandEnvelope <span class="token operator">=</span> <span class="token function">deserializeCommand</span><span class="token punctuation">(</span>resolvedEvent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>commandEnvelope<span class="token punctuation">.</span><span class="token function">isEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">var</span> handler <span class="token operator">:</span> handlers<span class="token punctuation">)</span> <span class="token punctuation">{</span> handler<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>commandEnvelope<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>See more in <a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/core/commands/ESDBCommandBus.java">ESDBCommandBus.java</a></p> <p>The business logic of the saga processing is delegated to aggregate. Thanks to that, we have a clear split of responsibility between coordination (saga) and business logic (aggregate). Thanks to that, the saga is lightweight and much easier to maintain than merging both into Process Manager.</p> <p>See the in <a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/hotelmanagement/groupcheckout/GroupCheckout.java">GroupCheckout</a> aggregate.</p> <p>We can check out the guest account if the balance is settled. If it’s not, then it will store the failure event:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">checkout</span><span class="token punctuation">(</span><span class="token annotation punctuation">@Nullable</span> <span class="token class-name">UUID</span> groupCheckoutId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span> now<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>status <span class="token operator">!=</span> <span class="token class-name">Status<span class="token punctuation">.</span>Open</span> <span class="token operator">||</span> balance <span class="token operator">!=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">enqueue</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">GuestAccountCheckoutFailed</span><span class="token punctuation">(</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> groupCheckoutId<span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span><span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">enqueue</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">GuestAccountCheckoutCompleted</span><span class="token punctuation">(</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> groupCheckoutId<span class="token punctuation">,</span> now<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>See more in <a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/hotelmanagement/gueststayaccount/GuestStayAccount.java#L48">GuestStayAccount</a> aggregate.</p> <p>We should use a retry policy in the command handler/application service to ensure that we won’t fail because of random transient errors.</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token class-name">ETag</span> <span class="token function">handle</span><span class="token punctuation">(</span><span class="token class-name">CheckoutGuestAccount</span> command<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> retryPolicy<span class="token punctuation">.</span><span class="token function">run</span><span class="token punctuation">(</span>ack <span class="token operator">-></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> result <span class="token operator">=</span> store<span class="token punctuation">.</span><span class="token function">getAndUpdate</span><span class="token punctuation">(</span> current <span class="token operator">-></span> current<span class="token punctuation">.</span><span class="token function">checkout</span><span class="token punctuation">(</span> command<span class="token punctuation">.</span><span class="token function">guestStayAccountId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">OffsetDateTime</span><span class="token punctuation">.</span><span class="token function">now</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">,</span> command<span class="token punctuation">.</span><span class="token function">guestStayAccountId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> ack<span class="token punctuation">.</span><span class="token function">accept</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>See more in <a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/hotelmanagement/gueststayaccount/GuestStayAccountService.java#L60">GuestStayAccountService</a></p> <p>As an alternative to the retry policy, we could do a <a href="https://www.dodgycoder.net/2011/11/yoda-conditions-pokemon-exception.html">Pokémon exception handling</a> in the command handler and then publish the failure event.</p> <h2 id="cross-module-processes-with-compensation" style="position:relative;"><a href="#cross-module-processes-with-compensation" aria-label="cross module processes with compensation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Cross-module processes with compensation</h2> <p>The previous example didn’t have a clear split between the internal and external events, as all of the processing was bounded in the same module. We didn’t need to map the internal terminology to the external world.</p> <p>We need to be careful when we’re doing cross-module coordination. E.g. Order Saga is initiated by the Shopping Cart module; then, it needs to initiate payment in shipment, which are handled by dedicated modules. If we exposed all the internal events outside, we would get the leaking abstractions. I wrote about that longer in <a href="https://event-driven.io/en/events_should_be_as_small_as_possible?utm_source=event_sourcing_jvm">Events should be as small as possible, right?</a>. We need to set the clear split between internal and external events and then map one into another.</p> <p>While doing that, we could do enrichment, as the external event can be less granular, as they should be understandable in the scope of the whole system (e.g. close to the result of <em>big picture</em> Event Storming session). Example enrichment can look like this:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartExternalEventForwarder</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">AggregateStore</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ShoppingCart</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartEvent</span><span class="token punctuation">,</span> UUID<span class="token punctuation">></span></span> store<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">EventBus</span> eventBus<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token class-name">ShoppingCartExternalEventForwarder</span><span class="token punctuation">(</span> <span class="token class-name">AggregateStore</span><span class="token generics"><span class="token punctuation">&lt;</span><span class="token class-name">ShoppingCart</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartEvent</span><span class="token punctuation">,</span> UUID<span class="token punctuation">></span></span> store<span class="token punctuation">,</span> <span class="token class-name">EventBus</span> eventBus <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>store <span class="token operator">=</span> store<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventBus <span class="token operator">=</span> eventBus<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> cart <span class="token operator">=</span> store<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span><span class="token function">shoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">orElseThrow</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Cannot enrich event, as shopping cart with id '%s' was not found"</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span><span class="token function">shoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> externalEvent <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ShoppingCartFinalized</span><span class="token punctuation">(</span> event<span class="token punctuation">.</span><span class="token function">shoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> cart<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> cart<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> cart<span class="token punctuation">.</span><span class="token function">totalPrice</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">confirmedAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> eventBus<span class="token punctuation">.</span><span class="token function">publish</span><span class="token punctuation">(</span>externalEvent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>See more in <a href=".src%5Cmain%5Cjava%5Cio%5Ceventdriven%5Cdistributedprocesses%5Cecommerce%5Cshoppingcarts%5Cexternal%5CShoppingCartExternalEventForwarder.java">ShoppingCartExternalEventForwarder.java</a>.</p> <p>Such events can be handled by the saga located in some modules. For instance:</p> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">OrderSaga</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token class-name">CommandBus</span> commandBus<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token class-name">OrderSaga</span><span class="token punctuation">(</span><span class="token class-name">CommandBus</span> commandBus<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>commandBus <span class="token operator">=</span> commandBus<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Happy path</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartFinalized</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">OrderCommand<span class="token punctuation">.</span>InitializeOrder</span><span class="token punctuation">(</span> event<span class="token punctuation">.</span><span class="token function">cartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">totalPrice</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">OrderInitialized</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">PaymentCommand<span class="token punctuation">.</span>RequestPayment</span><span class="token punctuation">(</span> UUID<span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">orderId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">totalPrice</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">PaymentExternalEvent<span class="token punctuation">.</span>PaymentFinalized</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">RecordOrderPayment</span><span class="token punctuation">(</span> event<span class="token punctuation">.</span><span class="token function">orderId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">paymentId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> event<span class="token punctuation">.</span><span class="token function">finalizedAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">OrderPaymentRecorded</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">ShipmentCommand<span class="token punctuation">.</span>SendPackage</span><span class="token punctuation">(</span> event<span class="token punctuation">.</span><span class="token function">orderId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">Arrays</span><span class="token punctuation">.</span><span class="token function">stream</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span>pi <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">ProductItem</span><span class="token punctuation">(</span>pi<span class="token punctuation">.</span><span class="token function">productId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pi<span class="token punctuation">.</span><span class="token function">quantity</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">toArray</span><span class="token punctuation">(</span><span class="token class-name">ProductItem</span><span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">::</span><span class="token keyword">new</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">ShipmentEvent<span class="token punctuation">.</span>PackageWasSent</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">CompleteOrder</span><span class="token punctuation">(</span> event<span class="token punctuation">.</span><span class="token function">orderId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Compensation</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">ShipmentEvent<span class="token punctuation">.</span>ProductWasOutOfStock</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">CancelOrder</span><span class="token punctuation">(</span> event<span class="token punctuation">.</span><span class="token function">orderId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">OrderCancellationReason<span class="token punctuation">.</span>ProductWasOutOfStock</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">on</span><span class="token punctuation">(</span><span class="token class-name">OrderCancelled</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span><span class="token function">paymentId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> commandBus<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token class-name">PaymentCommand<span class="token punctuation">.</span>DiscardPayment</span><span class="token punctuation">(</span> event<span class="token punctuation">.</span><span class="token function">paymentId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">DiscardReason<span class="token punctuation">.</span>OrderCancelled</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>If you look closer, you may notice that we’re also handling the negative scenarios. If the product was out of stock, we’re sending a command to cancel the order. Cancelling an order should trigger discarding payment (as we should return the money to the client if we can’t complete the order). This process is called compensation. Read more about its importance in <a href="https://event-driven.io/en/what_texting_ex_has_to_do_with_event_driven_design?utm_source=event_sourcing_jvm">What texting your Ex has to do with Event-Driven Design?</a>.</p> <p>Each module:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/ecommerce/shoppingcarts/">shopping carts</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/ecommerce/orders/">orders</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/ecommerce/payments/">payments</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/distributed-processes/src/main/java/io/eventdriven/distributedprocesses/ecommerce/shipments/">shipments</a>.</li> </ul> <p>can use dedicated event store streams to publish their external events, e.g. <em>‘shopping_carts__external-events’</em>. Other modules can subscribe to those streams and take it from there. It is a similar concept to Kafka’s topics.</p> <p>I explained also how to model distributed processes in detail in the <a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed">webinar about implementing distributed processes</a>:</p> <p><a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB8klEQVQoz4WST2sTURTF5wO59du49QO4kSBVQdwJbsRWwbiK1Z24UBQL8R8aCCQpaWtCkjYTJmmczLy/M4kxTX/yJk1sCsXF4XJ595173jnPG40ipFTEsVggEkgpkXJZJSIaoZRCa4PWOqtKacIwwhhLZfeAexv3+fD+G54xBgfrYC3GLvqLWBA5qFVVSjI/nbNdKPB08zG53AbecqtTIOIYLRXqrM/gLkuJ0RprE6xNscZikglCWWazGc/yeX62aty5ewsvikQ2HOmE6n6N+OAd0phse0bonj2eUu60qVWKNHc/U23u0f3+ClHf4c8cnufzFN/skLtxE8/5tpCvEUIgYx+pdearI7XOgu4hYbfDcdhnOOgy+DVgFDSJB0ecnM7ZerTF5tWXXLtyHc8RLTxSmCQl6vsMK2+JghZKGWpBiH1wm5PCE/RkitFnXidjtElIkoR2q037q0+5WMFzqWaESmVDUa/B8McLouYntJJ88UP62w8ZV0vIJM3m1sPRpGnKlN+EcfgvlJXCXpvj0mtCp7JTp+qH+JWPaHs+6XUoqbKg/K6P5+QvD4xNEIOAqFFCHJaRfp39fsxwr4QRMdpcTuqe3uv1nML1A+P8SSfZt9DJmNSFkk7OferLCYMgwHPJmoubM58WUKte/5ew0WjyFxc6K8nB1bJ+AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2023-09-22-webinar.png" srcset="/static/aeaac4b117eefd05e7a7793b42fef48d/36ca5/2023-09-22-webinar.png 200w, /static/aeaac4b117eefd05e7a7793b42fef48d/a3397/2023-09-22-webinar.png 400w, /static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2023-09-22-webinar.png 800w, /static/aeaac4b117eefd05e7a7793b42fef48d/e4699/2023-09-22-webinar.png 957w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p>Read also more in:</p> <ul> <li><a href="/en/saga_process_manager_distributed_transactions/">Saga and Process Manager - distributed processes in practice</a>,</li> <li><a href="/en/how_to_update_past_data_in_event_sourcing/">Oops I did it again, or how to update past data in Event Sourcing</a>,</li> <li><a href="/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/">Set up OpenTelemetry with Event Sourcing and Marten</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Is keeping dates in UTC really the best solution?]]>https://event-driven.io/en/is_keeping_utc_dates_best_solution/https://event-driven.io/en/is_keeping_utc_dates_best_solution/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a5d941268d739662b3e908063317075f/332ff/2022-07-06-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADYUlEQVQozwFWA6n8AMGmlcy/t7GjlZh9bL+Wf9KkhtHAt8/W1s7R0s2rl8ydf9iojMOUeZiOitLZ29ji4OLW1byxra2Eb7B9aADIzM3a2t7W2tzIsZrdrY2+qZnS2d3p5+fe4+i+sKrRn4DPpYq2jXRyamjb3N7l5eXm5eXDwsOyfWOzf2cAy8fE6unr0dbXqo963LCUvqCKoaKhzNPXrre7pYd5sZeGoJ6Yr5mLdV5Vc3uBvsPHq7K2mYN6sH5oX1VSAJiBcZGSko9+c8SSeteikduSeKmFdGJWUndcT7WJcYWFgdzk7bXBxYpuXYRiUmJSS15KQKF5ZXxsZE9VWwDAqJqpmpHCrJ3VopXc3eLhp6PPloXFtKjMsaTLu7CTioJ9hIaal5K9qJzMrJrCq5y7pZrKt6tpZGNSVVoAv5R60KKHz6KCuIhxtpmXoG1nsYJux6iVjnxyj4R+sJqMpYhzypV306uTzaqWxrCivaKRx6iVallTUFRbALCKcbiNdJd1YpV6ZWtTSYthTquGcH15eaKjqJ+iqIp+edysjc6olMTBwNTW2c3Nzr27ur+ahp19akFESQCfgmt6cmWJiH2Fg32Rjoadh3ebemR/f4Tw6u7j4OZ2d3ufg3O7uLXj1Nfrx8np3uDcw8W4qqO0hmp+X04AcmZaqqWbxsC+2NbUu7q1iomFgWpaZmNltbfAp6u0bGFem3llqqWi4t/i5a2t5LWy3dbZsaijk2pUlHBbAIV+dL27ur69vMnIxcjHxauur29jWoRvXUZFRklEQ5h2ZJl8aaGem+Lk6OTo6+Hj49zi5K+fmY1mU1VEPwCBfHakpaXQzszR0MzPzculp6lrXVaKcWJta2trbnGGbWGgemWHbF6fnp7S0tTLy8uxrq2Rd2hKPThHS1EAWFNRoaCcsbGuz87KxcPBf35+YUxEg4aLvsvS1tzjlZqjeWhfnHReb1dLYVpYXlNPWkc9e2BQMzY6nZWXAFNKRlZZXIyMiZual3p/gWVUTF5SULK5wa/JxMi/wMi+xXdta45nU49xYXliVpKNi6yjn39qXy8wM4WBgwBeS0M8MjIwLC85NTdMNTJ0UERSSUWorLPV09jSzdDAw8pzaGSaa1SUZk+MdWjOzM/My8yRhX47MC41NjuWne7ALrv9RAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/a5d941268d739662b3e908063317075f/a331c/2022-07-06-cover.png" srcset="/static/a5d941268d739662b3e908063317075f/36ca5/2022-07-06-cover.png 200w, /static/a5d941268d739662b3e908063317075f/a3397/2022-07-06-cover.png 400w, /static/a5d941268d739662b3e908063317075f/a331c/2022-07-06-cover.png 800w, /static/a5d941268d739662b3e908063317075f/332ff/2022-07-06-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>In many projects, the approach to dates is quite nonchalant.</strong> People do as they want. When on-premise systems were king, the common problem was that it was hard to know precisely when something happened. The consistency of the configuration depended on how meticulous ops people were. It wasn’t shocking to find out that the server had a different time zone, the application had a different one, and the user had a different time zone. At one point, the development community found a compromise that <em>“maybe we would use the same time zone everywhere, for instance <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time">UTC</a>”</em>.</p> <p><strong>This solution makes sense as we don’t do back-and-forth time conversions.</strong> The only thing we need to remember is to convert from local time to UTC somewhere on the frontend or when generating time on the server. Such an approach solves many problems but is quite romantic. It’s a typical example of programmers trying to find a generic solution to rule them all. We want a beautifully organized but artificial world in our code. Why an artificial one? Because users usually think in the context of local time. If I ordered an Uber drive to the airport in Wrocław, I flew to New York, and I order an Uber from the airport, I want to ensure that my application on the phone correctly selects the time zone and context in which I am.</p> <p><strong>What if time changes?</strong> What if the time zone changes? Will it never happen? Are you sure?</p> <p>As you can see in the tweet above, it happened even last year in Australia. Moreover, it also occurs in Poland every year when we change the winter time to summer time. Can you predict Daylight saving time change? Yes, you can. Yet, soon the countries in the European Union may not have a time change. So far, there is no consensus on when and how this will happen. One of the considered options is that each country can choose whether to stay in the summer or the winter time. If that comes true, we’ll have a loooot of time zone changes.</p> <p><strong>What could the consequences of this be?</strong> You book a cinema ticket for 10 o’clock. You order it in Poland, i.e. UTC + 2. So the reservation is made at 8 UTC. Let’s assume that our time zone will change (for example, by choosing summer or winter time). Let it change to +3. So our reservation converted from 8 o’clock UTC will be 11 o’clock local time. We’re doomed. We will come to the screening, and the film has been on for an hour (because its starting time has not changed, it goes at 10).</p> <p>Of course, not every business domain will have serious consequences. For example, if we arrive an hour late at the hotel, they will probably let us check-in. If any scheduled task is completed an hour later or earlier, it will probably not be a significant problem. Well, unless it is. If we do not predict it and don’t protect ourselves, even with a <em>dummy</em> time change, it may turn out that after withdrawing the hint, the task will be completed again (e.g. cyclical transfer).</p> <p><strong>Is this really a problem?</strong> It depends; it may or may not be. If your domain is based on scheduling and time is essential (e.g. reservations, transport, hotel industry, financial services), it is worth checking if we are prepared for this situation. We don’t want to end up with <a href="https://en.wikipedia.org/wiki/Year_2000_problem">Y2K problem</a>. It is, of course, still an edge case, but contrary to common belief, it happens much more often than we think.</p> <p><strong>How can you deal with it?</strong> Keeping UTC for past timestamps is fine. We can deduce time from historical knowledge. Yet, for future dates, it’s important to keep the local time zone or local date-time. Using tools like <a href="https://nodatime.org/">NodaTime</a>, <a href="https://www.joda.org/joda-time/">Joda time</a> (or alternatives) can be definitely helpful.</p> <p>It is worth considering and ensuring that our dates are as safe and straightforward as we think. We should aim to build simple solutions, but not simplistic ones.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Should you generate the client code from the API?]]>https://event-driven.io/en/client_code_generation/https://event-driven.io/en/client_code_generation/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/7fec91b6b694ef59bf6622bed34697bc/332ff/2022-06-29-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADdUlEQVQ4y2WPa0ybZRTH3w/GGD8Ys3jZaMFJIlsiJGoWk31wi26TjNGilDvbuEl2iSabG73MLciyRgJ0SLsimywiS5agETYKpexSTGYVGDhsRkLBUW6OQqH0wgiu1Z9534KXeJKTJ8855/c//yPIFRrWM1apRa785y9XqJEpP+HpN1Q8+fJ2ZGmnpdq/GSlFTqGWeEES+Vss2hDfWKWGTSknSSyopKCigcKKerZ/ZCZmX9maqJrYNN0ao1ljtAjydaG1gpTioEJDvOo0+Z9dpVh/mT2Hz5JR/jVbciukRdF53f+cCrFp0TNlqWppuzi8KSX6xih0JB8+x9tvbkMQBBJ25ZCQp0cmuhRNpKqJe+8Uce+fQa6MuhVEUHQVn1nO1vxzJBZWklRcxWsl1WzO05N99CzznRfprjpO/Ou7eT5FiyzlhMTIlFqe2/0hL+woIGbvMUlU2FH2Jan6FlSGNnKMFvZ/YSPf3Mn+eisqoxXzlWuE71lg9BZaQzNbSwy8klXOxpQyXtp3grjNWyT3T8QkEZd+BuGAKHChk1yjhey6dnLqLOSaOsipayerzkJpfSdj3S189+kh0k9WcrCxhyxDKztPNbHnSBW9DXps1R/zauI2Nip0COmGa6hq2zl46TZFl3vIM1tR1V4nx9SB6nwb2Q23eUuZzzMbXmSXxkymsVNalmGyort0nUVHK4x2odXpeeqd4whVlrv8PDGPsvAoSTv3cnfETfcv49L52qvfMzDh5cCRYzwbl8DNfic/jMxQctHGocab2O9P4RvtI+S04XKN0e5wIngW/UCE1m+/wfR5LSG/D8KrGKwDDI3/BvyJ484dztdUM+l+gBhf9Thp6xsBwiyHAgSDAVZWHkHkd4Txh17cswusRyC0zOSsl8EHs4zOzOOa9vBHJCz1Hq2sMLe4RP/YQ5wTHkQ2GAoRCoUIBgJMeRYQOobc9AxPsbocwOfz4fX5+dE1w/xSEPvwNK39Y1LPu7CI37/EwK+zTMz5GJ6ap9nhIhQM4A8EWA4GGHJ7EK7c6MV+zyU5iEQirD4Oc39yjpXVVdodQzTbfpJ6j8NRl4Oj08wu+hl0ualuufGfnniNkJGZiVqjobe3l66uLqxWKybTBZqamvigtJSiomL6+/uwWruw2+0YjUaqa2rQaHW8m5ws1UTO4XDQ2NjIX3w47Mkmws0VAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/7fec91b6b694ef59bf6622bed34697bc/a331c/2022-06-29-cover.png" srcset="/static/7fec91b6b694ef59bf6622bed34697bc/36ca5/2022-06-29-cover.png 200w, /static/7fec91b6b694ef59bf6622bed34697bc/a3397/2022-06-29-cover.png 400w, /static/7fec91b6b694ef59bf6622bed34697bc/a331c/2022-06-29-cover.png 800w, /static/7fec91b6b694ef59bf6622bed34697bc/332ff/2022-06-29-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I’m often told that dull, repetitive tasks should be automated. However, there are times when it is better not to do this. I regularly see that people want to generate client code from API. Especially typed ones, for instance, in TypeScript. When they ask me, <em>“Is it worth it?”</em> then, I often reply that I did it twice: the first one and the last one.</p> <p>There are many ideas that we find tempting at first. Later on, we regret that we have started to implement them. A lot of our decisions are made when we’re dumbest. We don’t know the business domain and nitty-gritty details of our technologies. Because of that, we tend to ignore the complexity of the problem. Unfortunately, later on, it doesn’t get better. Once we have done it, we instantly conclude that we will not make the same mistakes twice.</p> <p>What kills us most is the accidental complexity. It’s the one that we didn’t foresee. Usually related to some critical detail that’s blocking us. It’s a classic example of the <a href="https://en.wikipedia.org/wiki/Pareto_principle">Pareto principle</a>: we’ll do 80% of our work, spending 20% of the time. Then the remaining 20% will take us 80% of the time.</p> <p>Before I move on to the issues with client code generation from the API, let me discuss the potential benefits:</p> <ol> <li><strong>Single source of truth.</strong> We change the API definition in one place, which is then propagated to client applications.</li> <li><strong>Less copy/paste.</strong> Since we will generate the code automatically, we will no longer have to copy and paste it everywhere. We can close it in a package (e.g. NPM) and update it when the API changes.</li> <li><strong>We get rid of responsibility.</strong> This is especially true for backend developers. Lots of them believe that exposing an endpoint ends their work. They consider generating a client code as a bonus for which they should get a medal from the frontend team. New endpoint is exposed, deployed, TypeScript client code packaged, job done. Now all is left for the frontend team.</li> </ol> <p>If it is so beautiful, why is it so bad?</p> <ol> <li><strong>It turns out that there is no single source of truth.</strong> We realise that we have different API versions between dev, test, and production environments. The matter becomes even more complicated if we add parallel work. We rarely can always create API at first and then the work on the frontend. Usually, the works go in parallel. When we have part of the API from the feature branch and part of the main one, versioning becomes more and more difficult. How to version pre-release packages? How to know which one we should use? Should we use the package <em>1.0.0-beta + exp.sha.5114f85</em> or <em>1.0.0-beta + exp.sha.ae27z32</em>? If we add a microservices environment where each module can have its API, the brain starts spinning around.</li> <li><strong>The number of conflicts and typing fine-tuning becomes overwhelming.</strong> It’s splendid that the types have been generated, and we can easier call API. However, we’ll quickly start to get compilation errors after bumping the generated client package. That can be a benefit, as we immediately see what’s broken. Yet, too often, we get bonus updates from other colleagues changing the API. If a client is generated from the unified API Gateway, we may get bonus updates from other colleagues changing the API. In addition to the new field we wanted to add, we got a lot of changes from another module in the client package. The code doesn’t compile, we don’t know how to fix it, and another team doesn’t have time to do it yet. Then the first to update the client package becomes the lucky one. That’s not all. Updating the API behaviour is not only about modifying the contract. We usually do that together with the business logic changes. Those are even more challenging to track and fix. Especially if communication between teams doesn’t work well. It can quickly become a hot potato problem.</li> <li><strong>We can end up with a distributed monolith.</strong> A common practice in a distributed system is setting up a unified API Gateway. This allows client applications to use a unified interface for all APIs. Underneath, the call may be routed to a specific service. If we generate a standardised customer package from such an API, we will have the problems described that can escalate into unexpected directions. When we are overwhelmed by the magnitude of errors and unable to resolve constant conflicts after implementing the API, crazy ideas can come to mind. Although the backend is, in theory, ready for continuous delivery of changes, we start to panic mode. Suddenly, someone will shout: <em>“let’s introduce the release schedule!”</em> or <em>“let’s make code freeze!”</em>. We freeze implementations, set up the release train, etc. Of course, that won’t help but create even more issues. We solve the problem, not the root cause of it. We end up with microservices inside but a monolith as a whole. Our unified API and client generation will be the lowest common denominator pushing us into this dark place.</li> <li><strong>We still don’t have the guarantee that we are using the proper contract.</strong> If we have generated a new package, that doesn’t mean it is already used on the frontend. Even if it’s used, it doesn’t mean all endpoints are used and updated. There will never be a situation where the API will be unified with the client immediately. Even if we do über automation, which will automatically bump packages after API release, send PR, or event merge if tests pass, it will still be delayed. We never know if we have the current code until we bump the package. What’s more, we can’t be sure even if we do it. In the meantime, another one might have been published. There could also be an error in publishing the client package, and even though we have API changed, the package was not released.</li> <li><strong>Writing your own generator is fighting windmills.</strong> It will never be a high priority in maintenance. The use of someone else’s generator is, in turn, a fight against someone’s ideas and mismatches with our problem.</li> </ol> <p>When we use the generated code, we tend to subconsciously think the contract is valid. Manually changing contracts will not solve the problem magically. However, it already positions us into thinking that something may not be up-to-date.</p> <p>When generating client code make sense?</p> <ol> <li><strong>When we are not doing breaking changes</strong>. I wrote about that in <a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">Let’s take care of ourselves! Thoughts on compatibility</a>. The basic principle that should guide us is <a href="https://en.wikipedia.org/wiki/Primum_non_nocere">Primum non nocere</a>. When can you do breaking change? Never.</li> <li><strong>If we have a stable API or connect to an external API.</strong> <em>My API is stable</em> can be one of the famous last words, yet sometimes it’s stable enough to make client code generation manageable. External APIs always put us into downstream relations, so we must adapt and assume that we have to keep our clients up to date.</li> <li><strong>When we talk to each other.</strong> Nothing can replace a decent discussion about a contract’s definition. No code generation can replace the lack of design when teams don’t cooperate. As programmers, we must remember that making a functionality ends when the client starts using it. It does not end when we merge our code. It’s best to set a contract before starting development. Then many things are more manageable. Take a look at my article <a href="/en/sociological_aspects_of_microservices/">Sociological aspects of Microservices</a>. Implementing automated contract testing to catch accidental breaking changes is also substantial.</li> </ol> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Unobvious things you need to know about key-value stores]]>https://event-driven.io/en/key-value-stores/https://event-driven.io/en/key-value-stores/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a447c2161fecebed4dcceba95097e8f1/332ff/2022-06-22-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABsklEQVQoz2P4jw38+/cPmY3MRQYMaPy/f//9+fP3////7959vnvr+eOHr3/+/P3///8/v/8S0Ayx4dOnbxM6Nvg7NNnrVLgaV6dFTjx64CrY3L84NUN0fv78LSVqsop4gZ1WvZ1uo6V2o75ipZ5S4ea1JyDuwq4ZYnB323oLucL5gT15zvXNbs0L/NqXBXcVWDVb61c+uPcCTT8DsrVfv3z3cWj2168/X9zVFlSzNrzrbvrUh5lTpvv1acsUzpq8HeT5P3/RNUPMu3f3hYdprbN5m8Ps7b7JEyxr1ukuOK27+Ixl5ipbueKq4sX4bH7x4p2Heb2Xef2UJQ256eUlra3ta3s7N/ZmFXeYypQ2VS/DrhmuPzVqmqdB4Z0Fod05iTsm+L/daftxr9WC6gRNiZKNa49idzbcyLOn7pirF7sYlnqZNHmZ1nlbVPtY1pgq5ceH9H/9+gMt/TCgpZD////v2nbOybjOVLHYQqnMXLHcWKEkKXzSk0dvQDpxRRWy/qdPXs+dvrOudHFrzfItG05+w7ATu2ZISkRT9xcMCKdtWOCBnPDv77+/f//9xZErgJoBcvW3HTkgorQAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/a447c2161fecebed4dcceba95097e8f1/a331c/2022-06-22-cover.png" srcset="/static/a447c2161fecebed4dcceba95097e8f1/36ca5/2022-06-22-cover.png 200w, /static/a447c2161fecebed4dcceba95097e8f1/a3397/2022-06-22-cover.png 400w, /static/a447c2161fecebed4dcceba95097e8f1/a331c/2022-06-22-cover.png 800w, /static/a447c2161fecebed4dcceba95097e8f1/332ff/2022-06-22-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Today I wanted to talk a bit about Key-Value databases.</strong> It is a seemingly obvious subject, but it is easy to overlook the basic assumptions and have a spectacular failure in production.</p> <p>The general concept of such databases is straightforward. <strong>Data consists of a unique pair - key and value. Key is responsible for the uniqueness of the representation; the value can be anything:</strong></p> <ul> <li>binary data (e.g. serialised objects or even a video file),</li> <li>structured binary format (e.g. Protobuf or Avro),</li> <li>structured text file (JSON, XML),</li> <li>plain text</li> </ul> <p>or any other form of writing that comes to our mind depending on the specific implementation Database.</p> <p>Such databases start with elementary forms such as Azure Blob Storage or AWS S3 - where the value has no clear structure - these are bags/buckets for data. Interestingly, in S3, the key to your resource is unique worldwide. It cannot be repeated by any S3 users.</p> <p><strong>This uniqueness is essential.</strong> Keys are like trying to come up with your email while setting up a new mailbox account. We must think twice and come up with a pattern that will make it unique. Of course, we could generate a totally random email, but if someone wants to drop us a line, later on, they will have a problem finding it. In the end, the result looks like <em><a href="mailto:[email protected]">[email protected]</a></em>, <em><a href="mailto:[email protected]">[email protected]</a></em> or another format that will ensure that our email will not be repeated.</p> <p>The same we do with keys in key-value databases. To ensure the uniqueness of the key, we can use a random UUID (<em><a href="https://en.wikipedia.org/wiki/Universally_unique_identifier">Universally unique identifier</a></em>). We can also develop a specific format, such as <em>“CustomerId-ProjectNumber-TaskNumber”</em>, that can represent a particular task in a customer’s project.</p> <p><strong>The choice of the method for a key definition may have significant consequences.</strong> Using UUID will allow us to create a unique value by definition, but we must remember that we typically write the values ​​to read them later. UUID has a severe issue: it indexes poorly and may lead to bad performance. Why? Due to the nature of its randomness. These are entirely random values, so the database index will not be able to set a rule. Primary keys are typically <a href="https://docs.microsoft.com/en-us/sql/relational-databases/indexes/clustered-and-nonclustered-indexes-described?view=sql-server-ver16">clustered indexes</a>. That means that they laid out in memory to do effective seek operations. As the UUID keys are random, the database cannot find the pattern and index them efficiently. The potential solution to that is <a href="https://www.2ndquadrant.com/en/blog/sequential-uuid-generators/">sequential UUIDs</a>. They’re pseudo-randomised numbers. Typically they’re generated based on the timestamp from the server. Because of that, they can be well indexed by a database, as they’re generated using a particular algorithm. Yet, the uniqueness is only guaranteed if they’re generated on the same server.</p> <p><strong>As you know, tree structures are made for searching. Keys created with a specific format give us the advantage of using them to model a tree structure.</strong> Having the key defined with nested parts (e.g. with <em><a href="https://en.wikipedia.org/wiki/Uniform_Resource_Name">Unique Resource Name</a></em> format), we can make a quick traversal and find the specific record. We can use that to perform efficient filtering on key-value databases. We cannot use value for that because it can be anything. As mentioned above, it may be even a (more or less) random BLOB or plain text file. That means that it may not have any specific, uniform structure. Because of that, the database won’t be able to find a pattern to effectively search it. To filter records, the database would have to scan all the values, read them and check if they meet our criteria (e.g. if they contain a given piece of text). That’s clearly ineffective. Therefore, the proper key structure allows us to quickly navigate and find our records.</p> <p>For instance, having the <em>“CustomerId-ProjectNumber-TaskNumber”</em> key structure presented above, we can search find a project(s) for a specific customer or task(s) for a given project of a selected customer. It all looks pretty, but what if we want to search for all the tasks with a specific number? We would have to go through all the customers and their projects and then through tasks. We’re ending up with a well-known and disliked full scan. It’s like that because we know only the last part of the key, but the first part is unknown, so we cannot traverse through keys using a tree structure.</p> <p><strong>Despite appearances, key-value databases are not far from relational databases. In fact, the key-value is a relationship, a tuple.</strong> How do relational databases cope with searches? They create indexes. They’re creating additional lookups based on some criteria defined in the values. That speeds up reading but slows down writing (because we need to update lookups after each write). Quite a few key-value databases support some form of indexing, but…</p> <p>We should remember that key-value databases are most efficient when traversing the key tree structure. Thanks to that, they are incredibly fast because they can quickly go to a specific place in memory. It’s, in fact, a relational base stripped down to a bare minimum. Relational databases can be used for any form of data storage. They’ll do everything okayish. But they cannot reach the maximum optimisations because they need to support too many use cases.</p> <p><strong>Keys based on a tree structure also allow for much easier partitioning.</strong> Distributed multi-region/multi-tenant systems can benefit from that. The key can be used for “routing”: finding the location of the data. If we added to our key, for example, the prefix <em>“Continent-DataStoreName-CustomerId-ProjectNumber-TaskNumber”</em>, we would already know exactly where to get the data. This is how the mentioned Azure Blob Storage and AWS S3 work.</p> <p><strong>Okay, what are document databases?</strong> Document databases are key-value databases whose values ​​have a defined structure. That is why they are called <em>document</em>. They can be compared to paper applications we send to some government departments. They have specific fields with a set of possible values. Examples of such databases are <a href="https://martendb.io/documents/">Marten</a>, MongoDB, RavenDB. See also <a href="/en/strategy_on_migrating_relational_data_to_document_based/">General strategy for migrating relational data to document-based</a>.</p> <p><strong>A similar principle guides the wide-column databases.</strong> It is a step closer to the relational base. In such databases, we still store data in the form of key-value, but the values ​​themselves are stored in the form of columns. So each value is one row with columns. How is this different from relational databases? That each value can have a different set of columns. Examples include DynamoDB, CosmosDB, Azure Table Storage, and Cassandra.</p> <p>Redis and Elasticsearch are key-value databases. We can use such databases and not even know about that. Interestingly, for example, Kafka underneath is a key-value database wrapped with algorithms and techniques for data replication and consensus determination.</p> <p><strong><a href="/en/event_stores_are_key_value_stores">The last group of key-value databases are event stores.</a></strong> They have the same rules about keys as I explained above, but the value is a sequence of events (think the sequence of facts about the entity). Examples? <a href="https://martendb.io/events/">Marten</a>, <a href="https://www.eventstore.com/">EventStoreDB</a>, <a href="https://developer.axoniq.io/axon-server/overview">Axon Server</a> etc. Interestingly event stores can be modelled on top of other databases, so relational ones, key-value etc. So they’re kind of meta-databases.</p> <p>If we look closer, we’ll find that most of the systems we work on do not have relational data. Most of them reflect the physical process of documents’ workflows. Our clients often transfer physical processes to digital forms: invoices, orders, tickets, personal data, and already mentioned applications. These are all documents.</p> <p>So why do we use relational databases? Because they are good enough for many cases, popular, and suitable for advanced filtering, which is where relationships work great.</p> <p>Did you know that the first RDBMS was released in 1979 by Relational Software - today is known as Oracle. It was only in the 1980s that relational databases gained their popularity. Before that, object bases ruled. Also, NoSQL databases are nothing new. They are older than relational databases. The success of relational databases was that in the 1980s, every bit, byte, was worth its weight in gold - literally. The normalisation of relational databases significantly reduced the size of stored data. Now we don’t have this problem. Data storage is cheap. Information is priceless.</p> <p>Therefore, when making decisions, let’s remember the basics. Let’s check the nature of the data we store. Remember that we do not have to limit ourselves to one type of database. We can use different types tailored to a specific problem where it makes sense. Especially with emerging cloud solutions, operational costs are lower, and we can be more precise in modelling and tool selection. Thanks to that, our systems can be optimised and benefit in those places that need innovations most.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Ogooreck, a sneaky testing library in BDD style]]>https://event-driven.io/en/ogooreck_sneaky_bdd_testing_framework/https://event-driven.io/en/ogooreck_sneaky_bdd_testing_framework/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/bb0725f0da9d422e89b16784bea39bf1/332ff/2022-06-15-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABSklEQVQoz2P4jxf8+/8PQkIYaIABn85//379+nXxxunnL59AjSJS879/f////z9rVa9TlnR4tfm9x7f+////9+9fwpohio6eOpjdHrTqYJ9nqdKibZP+////5+9vwpr//QO5ML7YV0CeOarAzb9Wa9vR1WDNfwhohqjYsn+NV52kR5aKtAVrYI3e6zev4Ibi1AyR/vjhU3yzU+w0lbQ5Bu4N0v3LqkF++feXQID9BVvbv6Q+oE0mcZquT4OKZ4XKtduXCWuGSK/fvcyzWi59rm5oh5ZTpVjznDx44OPUDAnhqzcveFcqJ87QjOjW8mpUCKjWffj4PqZv0TVDzK6enhrcLRc/ycCnScm5VHbfse1wv+DVDE5ABf3hNqV8jhXirqXym/atwIweXDaDNN99eKtvWXXvkoozl479////95/feNIvgbSNP9sAABzh5HLaVPTvAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/bb0725f0da9d422e89b16784bea39bf1/a331c/2022-06-15-cover.png" srcset="/static/bb0725f0da9d422e89b16784bea39bf1/36ca5/2022-06-15-cover.png 200w, /static/bb0725f0da9d422e89b16784bea39bf1/a3397/2022-06-15-cover.png 400w, /static/bb0725f0da9d422e89b16784bea39bf1/a331c/2022-06-15-cover.png 800w, /static/bb0725f0da9d422e89b16784bea39bf1/332ff/2022-06-15-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Some time ago, I saw an excellent presentation of Dylan’s Beattie presentation - <a href="https://www.youtube.com/watch?v=6avJHaC3C2U">The Art of Code</a>. It reminds us of what we are here for. By <em>here</em> I mean in front of the computer. <strong>It shows the forgotten fun of discovering that computers can do what we tell them to</strong>.</p> <p>I sometimes forget about it in my daily routine. Our work can be very repetitive, HTML form here, HTML form there. Some time ago, I <a href="/en/12_things_I_learned_on_last_pull_request_review/">wrote about examples I did in Java</a>. I wrote them using (in my opinion) an interesting approach to testing. Based on <strong>Behavior-Driven Development</strong>. It is an approach to testing similar to TDD, but the accent lies elsewhere. Instead of technical tests (<em>Arrange / Act / Assert</em>), we’re focusing on the process, so business logic (<em>Given / When / Then</em>). This small change also allows us to better think about the API of the code we are writing.</p> <p>Many people think about them as writing UI tests (using tools such as <a href="https://cucumber.io/">Cucumber</a> using <a href="https://cucumber.io/docs/gherkin/">Gherkin</a> syntax). Some people believe that business people will write such tests. I consider it a pipe dream. I have never seen it working out in the long term. It usually ends with art for art’s sake. However, this does not change the fact that BDD principles are vital to me. I like when the tests focus on business (even when we test a purely technical class), so I’m concentrating on why we’re changing this code. I also like when my tests are a form of code documentation. I have not found a better form.</p> <p><strong>So, where is the fun I mentioned?</strong></p> <p>Tests are rarely associated with fun. I buy it, but writing your test tool is another beast. I sat down once on Friday night and Saturday morning and produced something like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Tests</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IClassFixture<span class="token punctuation">&lt;</span>ApiSpecification<span class="token punctuation">&lt;</span>Program<span class="token punctuation">></span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">GetProducts</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/products"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>OK<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">RegisterProduct</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> POST<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/products"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">RegisterProductRequest</span><span class="token punctuation">(</span><span class="token string">"abc-123"</span><span class="token punctuation">,</span> <span class="token string">"Ogooreck"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>CREATED<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">ApiSpecification<span class="token punctuation">&lt;</span>Program<span class="token punctuation">></span></span> API<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">Tests</span><span class="token punctuation">(</span><span class="token class-name">ApiSpecification<span class="token punctuation">&lt;</span>Program<span class="token punctuation">></span></span> api<span class="token punctuation">)</span> <span class="token operator">=></span> API <span class="token operator">=</span> api<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>…and I liked it!</p> <p><strong>I created the tool and named it: Ogooreck. A Sneaky Testing Library.</strong></p> <p>My idea is not to create an entire BDD framework but a tool that will allow you to write tests in this form. Not a tool that will replace others, but rather something simple but specific to specific usage scenarios. So, on the one hand, it is a general usage tool that can be expanded, but with a set of shortcuts that will allow you to write tests quickly and make them self-explanatory. For example, API tests, Event Sourcing, or just unit tests.</p> <p>Having that idea and base implementation, I decided to baptise it in fire, by refactoring my <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/136">EventSourcing.NET samples to use it</a>. That was a hell of a work, but it was worth it, as I wasn’t happy with them. Plus, it was a great chance to see how Ogooreck will play with the real world.</p> <p>To sum up. Main assumptions are :</p> <ul> <li>write tests seamlessly,</li> <li>make them readable,</li> <li>cut needed boilerplate by the set of helpful extensions and wrappers,</li> <li>don’t replace testing frameworks (works with all, so XUnit, NUnit, MSTests, etc.),</li> <li>testing frameworks and assert library agnostic,</li> <li>keep things simple, but allow compositions and extension.</li> </ul> <p><a href="/en/testing_event_sourcing/">It also supports testing business logic for multiple scenarios like</a>:</p> <ul> <li>CQRS,</li> <li>Aggregate,</li> <li>Event Sourcing,</li> <li>etc.</li> </ul> <p>It’s available on:</p> <ul> <li>GitHub: <a href="https://github.com/oskardudycz/Ogooreck">https://github.com/oskardudycz/Ogooreck</a>,</li> <li>NuGet: <a href="https://www.nuget.org/packages/Ogooreck">https://www.nuget.org/packages/Ogooreck</a>.</li> </ul> <p>OK, so how to use it?</p> <h2 id="api-testing" style="position:relative;"><a href="#api-testing" aria-label="api testing permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>API Testing</h2> <p>Ogooreck provides a set of helpers to set up HTTP requests, Response assertions. I recommend adding such <em>usings</em> to your tests:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">Ogooreck<span class="token punctuation">.</span>API</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token keyword">static</span> <span class="token class-name">Ogooreck<span class="token punctuation">.</span>API<span class="token punctuation">.</span>ApiSpecification</span><span class="token punctuation">;</span></code></pre></div> <p>Thanks to that, you’ll get cleaner access to helper methods.</p> <p>See more in samples below!</p> <h3 id="post" style="position:relative;"><a href="#post" aria-label="post permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>POST</h3> <p>Ogooreck provides a set of helpers to construct the request (e.g. <em>URI</em>, <em>BODY</em>) and check the standardised responses.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">POST_CreatesNewMeeting</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> <span class="token return-type class-name">POST</span> <span class="token function">URI</span><span class="token punctuation">(</span>"<span class="token operator">/</span>api<span class="token operator">/</span>meetings<span class="token operator">/</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateMeeting</span><span class="token punctuation">(</span>Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"Event Sourcing Workshop"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>CREATED<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3 id="put" style="position:relative;"><a href="#put" aria-label="put permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>PUT</h3> <p>You can also specify headers, e.g. <em>IF_MATCH</em> to perform an optimistic concurrency check.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">PUT_ConfirmsShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> <span class="token return-type class-name">PUT</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/ShoppingCarts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">API<span class="token punctuation">.</span>ShoppingCartId</span><span class="token punctuation">}</span></span><span class="token string">/confirmation"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">HEADERS</span><span class="token punctuation">(</span><span class="token function">IF_MATCH</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>OK<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3 id="get" style="position:relative;"><a href="#get" aria-label="get permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>GET</h3> <p>You can also do response body assertions, for instance, out of the box check if the response body is equivalent to the expected one:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">GET_ReturnsShoppingCartDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/ShoppingCarts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">API<span class="token punctuation">.</span>ShoppingCartId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> OK<span class="token punctuation">,</span> <span class="token function">RESPONSE_BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartDetails</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> API<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">,</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ClientId <span class="token operator">=</span> API<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> Version <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You can also use <em>GET_UNTIL</em> helper to check API that has eventual consistency.</p> <p>You can use various conditions, e.g. <em>RESPONSE_SUCCEEDED</em> waits until a response has one of the <em>2xx</em> statuses. That’s useful for new resource creation scenarios.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">GET_ReturnsShoppingCartDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/ShoppingCarts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">API<span class="token punctuation">.</span>ShoppingCartId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Until</span><span class="token punctuation">(</span>RESPONSE_SUCCEEDED<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> OK<span class="token punctuation">,</span> <span class="token function">RESPONSE_BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartDetails</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> API<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">,</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ClientId <span class="token operator">=</span> API<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> Version <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You can also use <em>RESPONSE_ETAG_IS</em> helper to check if ETag matches your expected version. That’s useful for state change verification.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">GET_ReturnsShoppingCartDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/ShoppingCarts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">API<span class="token punctuation">.</span>ShoppingCartId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Until</span><span class="token punctuation">(</span><span class="token function">RESPONSE_ETAG_IS</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> OK<span class="token punctuation">,</span> <span class="token function">RESPONSE_BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartDetails</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> API<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">,</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ClientId <span class="token operator">=</span> API<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> Version <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>You can also do more advanced filtering via <em>RESPONSE_BODY_MATCHES</em>. That’s useful for testing filtering scenarios with eventual consistency (e.g. having <em>Elasticsearch</em> as storage).</p> <p>You can also do custom checks on the body, providing expression.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">GET_ReturnsShoppingCartDetails</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">MeetingsSearchApi<span class="token punctuation">.</span>MeetingsUrl</span><span class="token punctuation">}</span></span><span class="token string">?filter=</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">MeetingName</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Until</span><span class="token punctuation">(</span> <span class="token generic-method"><span class="token function">RESPONSE_BODY_MATCHES</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IReadOnlyCollection<span class="token punctuation">&lt;</span>Meeting<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span> meetings <span class="token operator">=></span> meetings<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span>m <span class="token operator">=></span> m<span class="token punctuation">.</span>Id <span class="token operator">==</span> MeetingId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> <span class="token generic-method"><span class="token function">RESPONSE_BODY</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IReadOnlyCollection<span class="token punctuation">&lt;</span>Meeting<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>meetings <span class="token operator">=></span> meetings<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Contain</span><span class="token punctuation">(</span>meeting <span class="token operator">=></span> meeting<span class="token punctuation">.</span>Id <span class="token operator">==</span> MeetingId <span class="token operator">&amp;&amp;</span> meeting<span class="token punctuation">.</span>Name <span class="token operator">==</span> MeetingName <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3 id="delete" style="position:relative;"><a href="#delete" aria-label="delete permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>DELETE</h3> <p>Of course, the delete keyword is also supported.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">DELETE_ShouldRemoveProductFromShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> DELETE<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"/api/ShoppingCarts/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">API<span class="token punctuation">.</span>ShoppingCartId</span><span class="token punctuation">}</span></span><span class="token string">/products/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">API<span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>ProductId</span><span class="token punctuation">}</span></span><span class="token string">?quantity=</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">RemovedCount</span><span class="token punctuation">}</span></span><span class="token string">&amp;unitPrice=</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">API<span class="token punctuation">.</span>UnitPrice</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">HEADERS</span><span class="token punctuation">(</span><span class="token function">IF_MATCH</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>NO_CONTENT<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3 id="scenarios-and-advanced-composition" style="position:relative;"><a href="#scenarios-and-advanced-composition" aria-label="scenarios and advanced composition permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Scenarios and advanced composition</h3> <p>Ogooreck supports various ways of composing the API, e.g.</p> <p><strong>Classic Async/Await</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">POST_WithExistingSKU_ReturnsConflictStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Given</span> <span class="token class-name"><span class="token keyword">var</span></span> request <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RegisterProductRequest</span><span class="token punctuation">(</span><span class="token string">"AA2039485"</span><span class="token punctuation">,</span> ValidName<span class="token punctuation">,</span> ValidDescription<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// first one should succeed</span> <span class="token keyword">await</span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> POST<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/products/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>CREATED<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// second one will fail with conflict</span> <span class="token keyword">await</span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> <span class="token return-type class-name">POST</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/products/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>CONFLICT<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Joining with <em>And</em></strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">SendPackage_ShouldReturn_CreatedStatus_With_PackageId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span> POST<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/Shipments/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">SendPackage</span><span class="token punctuation">(</span>OrderId<span class="token punctuation">,</span> ProductItems<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>CREATED<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">And</span><span class="token punctuation">(</span>response <span class="token operator">=></span> fixture<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ShouldPublishInternalEventOfType</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>PackageWasSent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> @<span class="token keyword">event</span> <span class="token operator">=></span> @<span class="token keyword">event</span><span class="token punctuation">.</span>PackageId <span class="token operator">==</span> response<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetCreatedId</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>OrderId <span class="token operator">==</span> OrderId <span class="token operator">&amp;&amp;</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>SentAt <span class="token operator">></span> TimeBeforeSending <span class="token operator">&amp;&amp;</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span>Count <span class="token operator">==</span> ProductItems<span class="token punctuation">.</span>Count <span class="token operator">&amp;&amp;</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">All</span><span class="token punctuation">(</span> pi <span class="token operator">=></span> ProductItems<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span> expi <span class="token operator">=></span> expi<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> pi<span class="token punctuation">.</span>ProductId <span class="token operator">&amp;&amp;</span> expi<span class="token punctuation">.</span>Quantity <span class="token operator">==</span> pi<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><strong>Chained Api Scenario</strong></p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Post_ShouldReturn_CreatedStatus_With_CartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> createdReservationId <span class="token operator">=</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">;</span> <span class="token keyword">await</span> API<span class="token punctuation">.</span><span class="token function">Scenario</span><span class="token punctuation">(</span> <span class="token comment">// Create Reservations</span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> POST<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/Reservations/"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateTentativeReservationRequest</span> <span class="token punctuation">{</span> SeatId <span class="token operator">=</span> SeatId <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>CREATED<span class="token punctuation">,</span> response <span class="token operator">=></span> <span class="token punctuation">{</span> createdReservationId <span class="token operator">=</span> response<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetCreatedId</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> ValueTask<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// Get reservation details</span> _ <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span> GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/Reservations/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">createdReservationId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> OK<span class="token punctuation">,</span> <span class="token generic-method"><span class="token function">RESPONSE_BODY</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ReservationDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>reservation <span class="token operator">=></span> <span class="token punctuation">{</span> reservation<span class="token punctuation">.</span>Id<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>createdReservationId<span class="token punctuation">)</span><span class="token punctuation">;</span> reservation<span class="token punctuation">.</span>Status<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>ReservationStatus<span class="token punctuation">.</span>Tentative<span class="token punctuation">)</span><span class="token punctuation">;</span> reservation<span class="token punctuation">.</span>SeatId<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>SeatId<span class="token punctuation">)</span><span class="token punctuation">;</span> reservation<span class="token punctuation">.</span>Number<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">NotBeEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservation<span class="token punctuation">.</span>Version<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// Get reservations list</span> _ <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span> GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/Reservations/"</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> OK<span class="token punctuation">,</span> <span class="token generic-method"><span class="token function">RESPONSE_BODY</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>PagedListResponse<span class="token punctuation">&lt;</span>ReservationShortInfo<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>reservations <span class="token operator">=></span> <span class="token punctuation">{</span> reservations<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">NotBeNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservations<span class="token punctuation">.</span>Items<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">NotBeNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservations<span class="token punctuation">.</span>Items<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">HaveCount</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservations<span class="token punctuation">.</span>TotalItemCount<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservations<span class="token punctuation">.</span>HasNextPage<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reservationInfo <span class="token operator">=</span> reservations<span class="token punctuation">.</span>Items<span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservationInfo<span class="token punctuation">.</span>Id<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>createdReservationId<span class="token punctuation">)</span><span class="token punctuation">;</span> reservationInfo<span class="token punctuation">.</span>Number<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">NotBeNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>And<span class="token punctuation">.</span><span class="token function">NotBeEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservationInfo<span class="token punctuation">.</span>Status<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>ReservationStatus<span class="token punctuation">.</span>Tentative<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token comment">// Get reservation history</span> _ <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/Reservations/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">createdReservationId</span><span class="token punctuation">}</span></span><span class="token string">/history"</span></span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span> OK<span class="token punctuation">,</span> <span class="token generic-method"><span class="token function">RESPONSE_BODY</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>PagedListResponse<span class="token punctuation">&lt;</span>ReservationHistory<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>reservations <span class="token operator">=></span> <span class="token punctuation">{</span> reservations<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">NotBeNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservations<span class="token punctuation">.</span>Items<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">NotBeNull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservations<span class="token punctuation">.</span>Items<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">HaveCount</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservations<span class="token punctuation">.</span>TotalItemCount<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservations<span class="token punctuation">.</span>HasNextPage<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reservationInfo <span class="token operator">=</span> reservations<span class="token punctuation">.</span>Items<span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> reservationInfo<span class="token punctuation">.</span>ReservationId<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Be</span><span class="token punctuation">(</span>createdReservationId<span class="token punctuation">)</span><span class="token punctuation">;</span> reservationInfo<span class="token punctuation">.</span>Description<span class="token punctuation">.</span><span class="token function">Should</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">StartWith</span><span class="token punctuation">(</span><span class="token string">"Created tentative reservation with number"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="xunit-setup" style="position:relative;"><a href="#xunit-setup" aria-label="xunit setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>XUnit setup</h3> <p>Although Ogooreck is testing framework agnostic, the initial tests were made for XUnit. If you’re using it, here are some hints on how to use it.</p> <h3 id="injecting-as-class-fixture" style="position:relative;"><a href="#injecting-as-class-fixture" aria-label="injecting as class fixture permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Injecting as Class Fixture</h3> <p>By default, I recommend injecting <em>ApiSpecification<YourProgram></em> instance as <em>ClassFixture</em> to ensure that all dependencies (e.g. <em>HttpClient</em>) will be appropriately disposed.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CreateMeetingTests</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IClassFixture<span class="token punctuation">&lt;</span>ApiSpecification<span class="token punctuation">&lt;</span>Program<span class="token punctuation">></span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ApiSpecification<span class="token punctuation">&lt;</span>Program<span class="token punctuation">></span></span> API<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">CreateMeetingTests</span><span class="token punctuation">(</span><span class="token class-name">ApiSpecification<span class="token punctuation">&lt;</span>Program<span class="token punctuation">></span></span> api<span class="token punctuation">)</span> <span class="token operator">=></span> API <span class="token operator">=</span> api<span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">CreateCommand_ShouldPublish_MeetingCreateEvent</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span>When <span class="token punctuation">(</span> POST<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span>"<span class="token operator">/</span>api<span class="token operator">/</span>meetings<span class="token operator">/</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">CreateMeeting</span><span class="token punctuation">(</span>Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token string">"Event Sourcing Workshop"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>CREATED<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="setting-up-data-with-iasynclifetime" style="position:relative;"><a href="#setting-up-data-with-iasynclifetime" aria-label="setting up data with iasynclifetime permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Setting up data with <em>IAsyncLifetime</em></h3> <p>Sometimes you need to set up test data asynchronously (e.g. open a shopping cart before cancelling it). You might not want to pollute your test code with test case setup or do more extended preparation. For that XUnit provides <em>IAsyncLifetime</em> interface. You can create a fixture derived from the <em>APISpecification</em> to benefit from built-in helpers and use it later in your tests.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GetProductsFixture</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ApiSpecification<span class="token punctuation">&lt;</span>Program<span class="token punctuation">></span></span><span class="token punctuation">,</span> <span class="token class-name">IAsyncLifetime</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">List<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span></span> RegisteredProducts <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">GetProductsFixture</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">WarehouseTestWebApplicationFactory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">InitializeAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productsToRegister <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RegisterProductRequest</span><span class="token punctuation">(</span><span class="token string">"ZX1234"</span><span class="token punctuation">,</span> <span class="token string">"ValidName"</span><span class="token punctuation">,</span> <span class="token string">"ValidDescription"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RegisterProductRequest</span><span class="token punctuation">(</span><span class="token string">"AD5678"</span><span class="token punctuation">,</span> <span class="token string">"OtherValidName"</span><span class="token punctuation">,</span> <span class="token string">"OtherValidDescription"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RegisterProductRequest</span><span class="token punctuation">(</span><span class="token string">"BH90210"</span><span class="token punctuation">,</span> <span class="token string">"AnotherValid"</span><span class="token punctuation">,</span> <span class="token string">"AnotherValidDescription"</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> registerProduct <span class="token keyword">in</span> productsToRegister<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> createdId <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>POST<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/products"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">BODY</span><span class="token punctuation">(</span>registerProduct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>CREATED<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetCreatedId</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> _<span class="token punctuation">)</span> <span class="token operator">=</span> registerProduct<span class="token punctuation">;</span> RegisteredProducts<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductListItem</span><span class="token punctuation">(</span>createdId<span class="token punctuation">,</span> sku<span class="token operator">!</span><span class="token punctuation">,</span> name<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">DisposeAsync</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> Task<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GetProductsTests</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IClassFixture<span class="token punctuation">&lt;</span>GetProductsFixture<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">GetProductsFixture</span> API<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">GetProductsTests</span><span class="token punctuation">(</span><span class="token class-name">GetProductsFixture</span> api<span class="token punctuation">)</span> <span class="token operator">=></span> API <span class="token operator">=</span> api<span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">ValidRequest_With_NoParams_ShouldReturn_200</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token string">"/api/products/"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>OK<span class="token punctuation">,</span> <span class="token function">RESPONSE_BODY</span><span class="token punctuation">(</span>API<span class="token punctuation">.</span>RegisteredProducts<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">ValidRequest_With_Filter_ShouldReturn_SubsetOfRecords</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> registeredProduct <span class="token operator">=</span> API<span class="token punctuation">.</span>RegisteredProducts<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> filter <span class="token operator">=</span> registeredProduct<span class="token punctuation">.</span>Sku<span class="token punctuation">[</span><span class="token number">1</span><span class="token range operator">..</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">return</span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/products/?filter=</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">filter</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>OK<span class="token punctuation">,</span> <span class="token function">RESPONSE_BODY</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span></span> <span class="token punctuation">{</span> registeredProduct <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">ValidRequest_With_Paging_ShouldReturn_PageOfRecords</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Given</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">int</span></span> page <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">int</span></span> pageSize <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> pagedRecords <span class="token operator">=</span> API<span class="token punctuation">.</span>RegisteredProducts <span class="token punctuation">.</span><span class="token function">Skip</span><span class="token punctuation">(</span>page <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span>pageSize<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/products/?page=</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">page</span><span class="token punctuation">}</span></span><span class="token string">&amp;pageSize=</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">pageSize</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>OK<span class="token punctuation">,</span> <span class="token function">RESPONSE_BODY</span><span class="token punctuation">(</span>pagedRecords<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Fact</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">NegativePage_ShouldReturn_400</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/products/?page=</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp"><span class="token operator">-</span><span class="token number">20</span></span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>BAD_REQUEST<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Theory</span></span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">InlineData</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">InlineData</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token operator">-</span><span class="token number">20</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">NegativeOrZeroPageSize_ShouldReturn_400</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> pageSize<span class="token punctuation">)</span> <span class="token operator">=></span> API<span class="token punctuation">.</span><span class="token function">Given</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>GET<span class="token punctuation">,</span> <span class="token function">URI</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/products/?pageSize=</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">pageSize</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Then</span><span class="token punctuation">(</span>BAD_REQUEST<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Read also:</p> <ul> <li><a href="/en/behaviour_driven_design_is_not_about_tests/">Behaviour-Driven Design is more than tests</a></li> <li><a href="/en/testing_event_driven_projections/">How to test event-driven projections</a></li> <li><a href="/en/writing_and_testing_business_logic_in_fsharp/">Writing and testing business logic in F#</a></li> <li><a href="/en/testing_event_sourcing/">Testing business logic in Event Sourcing, and beyond!</a></li> <li><a href="/en/i_tested_on_production/">I tested it on production and I’m not ashamed of it</a></li> </ul> <h2 id="credits" style="position:relative;"><a href="#credits" aria-label="credits permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Credits</h2> <p>Last but not least, special thanks go to:</p> <ul> <li>Simon Cropp for <a href="https://github.com/SimonCropp/MarkdownSnippets">MarkdownSnippets</a> that I’m using for plugging snippets to markdown,</li> <li>Adam Ralph for <a href="https://github.com/adamralph/bullseye">BullsEye</a>, which I’m using to make the build process seamless,</li> <li><a href="https://mysticmind.dev/">Babu Annamalai</a> that did a similar build setup in <a href="https://martendb.io/">Marten</a> which I inspired a lot,</li> <li>Dennis Doomen for <a href="https://fluentassertions.com/">Fluent Assertions</a>, which I’m using for internal assertions, especially checking the response body.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Should you throw an exception when rebuilding the state from events?]]>https://event-driven.io/en/should_you_throw_exception_when_rebuilding_state_from_events/https://event-driven.io/en/should_you_throw_exception_when_rebuilding_state_from_events/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6e741ed052523b895f1103a39d45d706/332ff/2022-06-08-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACaElEQVQoz2NgoCJgZGRkYGBQV5DM9zerDjaItxCV4mUGiROpU1JUYHN74sNJQVfa3fflaxfYCTERaS0zE1NNvMv6LMNzlWaXmhzmh0p1+Yk7KrCBZQlZq6si3RWpvyBQ5FiB7rEi/Q0p6ssTlatdRQRYGHA6ngmsVUtBoi3VrcFTemWExKlC3R3p6nsyVXu9hGdFKniosLMwMjAxolvIwAy2k4eTLdJNrzNav8dbdHWcwvoYxcVh8htSVLs9BacHShbZCinyMUHUo9vPy8liJM+f4W04LdViYoDEmlTNHRma6+MUl8Qp9XoJllvxpBhzu6lw6ImzMjMyIPwvwMVmocQXqs9b4iA0JcGkK1C5yVlgcbzq9gKTrTn6i+KViixYcwzYozVZgtSZE3SZ/FSYJLkZof731xII1uaJ0mIKUGAMVWGMUWOtseWeHSm/LEVzaYrmygztOifeXFPOCA0OD2nGYBWGSE0GXwVGWT5GkHZdPiZrEQYXCQYrAQYPCWYfSaZ0HZZpoZIrUrUWxqusytSdEiGXYcDsIc1sycfgKskQpMIQocEQrcVgKsHMIMbEYCHCainJYy3BYSPG6aPAHaTCUeclOzVWe0qM+sRYzUkJ+inmQh5KXDr8XJYSnN7KHK7SDI7SDLYiDAxcDAxa0pJTJ/Zv3b69s6m+t6Nt7bp1a9as7muuntdVuWnbzvkzpzQUZy5etHj27NnTps9orCwz5mHQ4WbU52NgEGRgUOZkDXG0Lk2Lj3O3Tw3yzk1MrMtJ7i2JaUzxnFqfPak4LN5JJ9rbMysmqiA+PN7DylyAwVWK0U6CAQARXJjnU35BcAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/6e741ed052523b895f1103a39d45d706/a331c/2022-06-08-cover.png" srcset="/static/6e741ed052523b895f1103a39d45d706/36ca5/2022-06-08-cover.png 200w, /static/6e741ed052523b895f1103a39d45d706/a3397/2022-06-08-cover.png 400w, /static/6e741ed052523b895f1103a39d45d706/a331c/2022-06-08-cover.png 800w, /static/6e741ed052523b895f1103a39d45d706/332ff/2022-06-08-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Event Sourcing is not a difficult pattern to understand.</strong> Event stores are not conceptually complicated databases (although implementation may be quite complex). We have two data structures in them:</p> <ul> <li><strong>event</strong>, a fact that has occurred in our system. For instance: the order has been started, the shopping cart has been confirmed, a cinema seat has been reserved,</li> <li><strong>events stream</strong>, an ordered sequence of events. The stream conceptually corresponds to the record/entity/aggregate in the classical approach. Typically, one record (e.g., one reservation, one order) is represented by one stream. In other words, these are all facts recorded for a given object.</li> </ul> <p>Additionally, we have two types of operations:</p> <ul> <li>append an event to the end of the selected stream,</li> <li>read all events for the stream.</li> </ul> <p>That’s all, and that’s enough to implement the simplest event store. <a href="/en/lets_build_event_store_in_one_hour/">I even showed how to do it in one hour!</a>.</p> <p><strong>In Event Sourcing, each business operation ends with the registration of its results, i.e. a new event.</strong> To execute business logic, we need to know its current state. <strong>In Event Sourcing, the state is events.</strong> We don’t have to make a revolution, and we can still use entities and a “flattened” current entity state. We need to turn the sequence of events into an object to get it. How to do it? I wrote in detail in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a>. A short TLDR:</p> <ul> <li>we read all events from the stream in the order of occurrence,</li> <li>create a default entity object (e.g. using the default constructor),</li> <li>for each event, we apply it to the entity state to get the current one.</li> </ul> <p>This can be done, for example, by adding the <em>Apply</em> methods for each type of event. For example, a method that applies an event to remove a product from the cart could look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItemToBeRemoved <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem<span class="token punctuation">.</span><span class="token function">HasTheSameQuantity</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">Subtract</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>In short, when we <em>zero</em> the number of products in the shopping cart, we remove the whole product item. If we are removing only a part, we reduce the number. <strong>What if the product, for some reason, does not exist in the cart?</strong> I wanted to talk about that case today.</p> <p>Take a look at the snippet that just ignores this scenario.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span></code></pre></div> <p>Wouldn’t you be tempted to throw an exception here? Yes, I would also be tempted. So why didn’t I do it?</p> <p>Let us recall once again. Events are facts. They happened. What has been seen will not be displayed. <strong>The events were stored in our database because our business logic stated that rules are fulfilled, and we can perform the operation and record the event as a result.</strong> If we trust our business logic (read: we have it tested), we should also trust the recorded event. Yes, I know control is the basis of trust, plus I don’t trust myself either.</p> <p><strong>So what if we forgot some IF in our logic, or we misspelt the event?</strong> What if our logic in the past allowed us to remove more products than existed in the shopping cart? Sounds absurd? Perhaps, but I would like to remind you that most bank accounts have an overdraft facility, the amount of which can be changed. Moreover, there may be a legal rule prohibiting banks from using overdrafts. If we change our application method, forgetting that it was possible in the past, we will cause errors in the system’s operation.</p> <p><strong>So am I suggesting that we ignore data errors?</strong> God forbid! Mistakes need to be fixed (not only those in the code). I wrote about it in <a href="/en/what_texting_ex_has_to_do_with_event_driven_design/">What texting your Ex has to do with Event-Driven Design?</a>. The best way to correct a mistake is to regret your sins and promise correction. Well, the best thing is actually improving. In Event-Driven Architecture, this is best done by adding another event. Thanks to that, we can be sure that even in a distributed system, information about the compensation will be propagated between independent services.</p> <p><strong>What could be a compensating event?</strong> If, for instance, the cashier in a department store charged us with the same product twice, the money will be refunded, and a new receipt will be generated. Do you think that’s a hack? Let me dispel this way of thinking. This is not a hack; this is how business works, especially where paper is involved. You do not scribble on the contract; you print a new one and then sign it. It is also worth thinking about compensating actions before the end of development. <a href="/en/no_it_can_never_happen/">The more we give the user the chance to fix errors on their own, the better</a>. We can even consider introducing <em>administrative</em> events so that we can correct our mistakes without redeploying the application.</p> <p>When we publish a compensating event, it will be appended like any other: to the current end of the stream. <strong>So, if we throw an exception on an event that had a bug, we won’t give ourselves a chance to fix the state.</strong> We won’t reach the compensating event because each time we try to apply the “wrong event” on the state, we will throw an exception.</p> <p>On the other hand, even if we did not have compensating events, should the actual recovery be verifying the business rules? Business logic should be checking them. If our state is wrong, it should pick up the inconsistency. Therefore, you can simply check whether the state is consistent and meets the business rules after restoring the state from events and then run the appropriate business logic. Of course, we will not capture everything this way because the state may meet the business rules and not be correct (e.g. with double charges made by the cashier), but that’s another story.</p> <p><strong>So, do I suggest that you always ignore mistakes?</strong> It depends. In my opinion, throwing exceptions may make sense in the initial development of functionality when the functionality is <em>stabilizing</em>. At such a stage, we may want to know as soon as possible that something went wrong. We can also assume that only when there is the first need to introduce a compensating operation then, we remove code that throws exceptions to unblock it. However, we must remember one thing. If we decide to do so, the error will be corrected only after deployment. Which can mean in the extreme situation of a wake-up call in the middle of the night if you are the unfortunate one on the shift.</p> <p><strong>One IF, and so much fun, isn’t it?</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Why I'm leaving Event Store and getting ready for the next episode]]>https://event-driven.io/en/leaving_event_store/https://event-driven.io/en/leaving_event_store/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d0fc31a111a34c8d7073b7b9dad8a2b1/332ff/2022-06-01-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADYUlEQVQozwFWA6n8ANfi8ezv9/X2+PX2+Pb2+PX19/n5+/Dv8dbX2O/v8vf3+fb2+Pb2+Pb2+PLz99Xi8brS6rPP6L7V69zm8wDg6PPw8vj19fj19fj29vj09Pb//v9sbm8iFxPUzMn6+/3z8/f19vn09fne5/PI2+7J2+7V4vDn7fX29vkA8fL48/T59/f59/b39PX28/T3+/7/UUxKQyMX39bS////7O/16+/26Oz12ePx0+Dv6O328/P67vD47/H5AM7e8M/f8NDf8Obt9vL0+fX3/dbT0GBWRyskF5aUjObm68vb7M3d7s/e78jb783e797n8+Tq9dji8dfj8QDH2+7D2O2+1OzG1ubZ4ezl7/6djHZOPiVjcXBrdHlEMCO2vcjI4PXK3O3M3e/L3O/H2e6/1eyzz+i30OkAydvuzN3u2eXzTVFTLS0qYWNcWVNCgVUzamppQUNGOikZZ293yOH5vdLqvNTrt9Hqtc/osM3mrcznr8zmALfQ6LbR66PC2xUgKQAAAgMICQUMER0UDRIIBA8KBiQaEwEBAXyWq6zQ76LF46TH5aTH5aLG5Z/E5ZrB4wDD2ezP3eunsbyGi5KKjpiFjJiAgo18d3uEf4CRjo+EgICCeXioqKvW4ezU3unX3+vX4OzT3OfT3OjS3ekArJuW4NDL+erl/e/p//Xu//Xw//bw//by//j0//fz//Pu//by/fLt+uzm/O/p/vHt/vDs9+nj+evl/e/rAMa6t+PW0+3h3PDk3u3i3t7SzdHGxNjNydPJxfPp5fPq5/Pm3vHi1vDczvbp5Pnv7/Xq4/Hm4PTp5fjv7AD56+n/9vT16ufl2NXz5ODRwb3BtLG/s7DTxMH05N/m2NLs2s/y28zrvJHrz7n57+746+fm2NPl2NLx4t8AkoSAi4B8n5KOqZuXZ2loXmRlcnJxcW9tbG1qbmxooZONjIF8ZmJdZlhLgHFldG5qXVtYhHx2q5yVaWZhAAkKCxMiLGpvdcK1rhEnKwAKDjo9PTI/PhgoKDtCOripomllXwAJCBkdHVdVTRYbGQAAAGNiX82/tzpGPwA/RUiXsse5wc3Du7czR08mNT9gbXUiIB0GCQsyNS68rqeeoaNbcHlhb3WCjZExQEcjOUR3h5DJvrlOb3uDJEg7motrZQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/d0fc31a111a34c8d7073b7b9dad8a2b1/a331c/2022-06-01-cover.png" srcset="/static/d0fc31a111a34c8d7073b7b9dad8a2b1/36ca5/2022-06-01-cover.png 200w, /static/d0fc31a111a34c8d7073b7b9dad8a2b1/a3397/2022-06-01-cover.png 400w, /static/d0fc31a111a34c8d7073b7b9dad8a2b1/a331c/2022-06-01-cover.png 800w, /static/d0fc31a111a34c8d7073b7b9dad8a2b1/332ff/2022-06-01-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I’ve got some news today. Here comes the boom: <strong>I’m leaving the Event Store</strong>, which means I just became unemployed!</p> <p><strong>How come?</strong></p> <p>I am a pragmatic person. My usual decisions are entirely rational. Obviously, this is not the case here. Working at the Event Store was a massive opportunity for me. I wrote about it in more detail in the article <a href="/en/revolution_now/">Revolution now!</a>. TLDR: It allowed me to work daily with the leading Event Sourcing solution. It also enabled me to do what I like: help the community, explain the benefits of Event Sourcing and help to solve learning issues. It also was a chance to master EventStoreDB knowledge. And that’s what it was. The last year and a half was a lot of learning and an excellent possibility to show people what I learned. If it was so good, why am I leaving?</p> <p><strong>A Developer Advocate’s job is not as sexy as it may seem from the side.</strong> It’s not only about conferences, after-parties, webinars and blogging. During the first months, I focused on improving <a href="https://developers.eventstore.com/clients/grpc/">documentation for gRPC clients</a>. It needed some love. <a href="https://www.youtube.com/watch?v=HQmmM_qwG4k">Whole lotta love</a>. It was quite basic with partial examples in .NET when I joined the team. EventStoreDB now provides <a href="https://github.com/EventStore/EventStore-Client-Dotnet">.NET</a>, <a href="https://github.com/EventStore/EventStoreDB-Client-Java">Java</a>, <a href="https://github.com/EventStore/EventStore-Client-NodeJS">NodeJS</a>, <a href="https://github.com/EventStore/EventStoreDB-Client-Rust">Rust</a>, <a href="https://github.com/EventStore/EventStore-Client-Go/">Go</a> clients (plus unofficial ones). They required also love and work to align the used conventions. We intended to maintain local environment specialities while keeping a similar developer experience. To present them well in documentation, they had to be standardised first. I rolled up my sleeves myself and helped unify the clients:</p> <ul> <li>NodeJS, <a href="https://github.com/EventStore/EventStore-Client-NodeJS/pulls?q=is%3Apr+is%3Aclosed+author%3Aoskardudycz">see more</a>,</li> <li>Java, <a href="https://github.com/EventStore/EventStoreDB-Client-Java/pulls?q=is%3Apr+is%3Aclosed+author%3Aoskardudycz">see more</a>,</li> <li>Go, getting the community call to help us deliver something that Gophers will like, <a href="https://github.com/EventStore/EventStore-Client-Go/pull/65">see more</a>.</li> </ul> <p>I continued the effort also with other clients. I also helped build the internal ADR and RFC process by discussing and providing my and community feedback. This process has matured, and I hope that at least some of these design documents will be published soon. I think my enigmatic title of Developer Advocate had the justification in such type of work. My main goal was to <a href="/en/small_rant_about_software_design/">make things accessible</a> and show that our tools (and Event Sourcing in general) can be adequate for broader adoption.</p> <p>In addition, I wrote dozens of blogs and appeared many times at webinars and conferences and a few podcasts. I laughed that I’ll be there asking if you want to talk about Event Sourcing popping out from your fridge.</p> <p><strong>Okay, but why am I leaving?</strong></p> <p>It was definitely a tough decision to make that required getting out of my comfort zone. It involved many considerations. Working at the Event Store was a good experience. There’s no “bad blood” between us. In fact, we’re discussing some lightweight forms of collaboration. Nevertheless, I want to go beyond the canon of what I have been doing and better implement my vision. Let’s face it, a Developer Advocate’s job is marketing for developers. Of course, we can put it in different words, but that’s the fact. Of course, I tried to do it objectively without evangelising, explaining the pros and cons. Still, we are not expected to be creative but re-creative. <strong>I like to create new things. I like to deliver. I would like to do it more.</strong></p> <p>Perhaps even more important, the second reason is the need to get more flexibility with my time. Or simply putting: <strong>spending more time with your family.</strong> COVID indeed prevented me from travelling around the world to conferences. However, the work of a Developer Advocate is still mentally engaging. It’s not easy to find calm, focus time. It requires constant contact with the community and coworkers. It’s not easy to find the right balance, especially in remote work, where you don’t see people in person but through the Zoom window.</p> <p>Plus, I usually finish my workday around 5 PM; my daughter is 2.5 years old. We’re generally getting her ready to sleep around 7 PM, which effectively gave me 2 hours of fun with her. Not enough. Definitely not enough. And it is also due to fatigue. I want to adjust better and start to get a better work-life balance. Which I constantly struggle with. And overcome my workaholism and teetering on the edge of burnout.</p> <p><strong>What’s next? How will I earn a living?</strong></p> <p>I plan to start with consulting, workshops, etc. I did not advertise myself so far, but practically every month, I got inquiries about consultancy, workshops and other forms of support. So, <strong>it is also an opportunity for you and your company if you want to enter the land of event-based architectures.</strong> I’m open to cooperation. I’m not yet ready for full-time or long-term work, but if I could help you, <a href="mailto:[email protected]">feel free to reach me</a>.</p> <p><strong>And here comes the massive boom! In July, I want to finally start pre-ordering the online Event Sourcing course.</strong></p> <p>There, I said it. I have so far received positive feedback about the idea. Still, I’d like to do validation before fully committing my time to that. Because it will definitely be a significant effort, plus there is simply no such material on the web, so I want to do it with a bang. I hesitated for a few years, so now I’ll just do it and see what comes out of it. I plan to explain how to transition from CRUD to Event Sourcing in the course. I intend to explain why would you consider doing that and how you can benefit from it. This will allow me to show the entire journey with all the nuances. By explaining how to apply it in your current project, I will also provide enough knowledge for a new one. <strong>Would you be interested? If yes, <a href="mailto:[email protected]">drop me an email</a> or comment under this post about what would make you buy it.</strong> Sign also to <a href="https://www.architecture-weekly.com/">Architcture Weekly</a>.</p> <p>I’d also like to get into paid developer tooling around EDA; if there’s a tool that would help you, feel free to tell me, and I might consider building such one. Crazy ideas also count!</p> <p>I’ve already started building my paid community around <a href="https://github.com/sponsors/oskardudycz">GitHub sponsors</a> and <a href="https://www.architecture-weekly.com/">Architcture Weekly</a>. So far, it’s not a high income, but already enough to pay rent. <strong>Over 20 people have already joined, and I encourage you to do the same!</strong> I think it’s a fantastic place and community to gather knowledge and exchange ideas. We have a monthly webinar, Discord channel with direct access to me: <a href="https://github.com/sponsors/oskardudycz">https://github.com/sponsors/oskardudycz</a>.</p> <p>In short, I’ll try different things and see what sticks. If it doesn’t work out, I will return to my job. I don’t know if it was a good decision, but I knew I needed a change. I’ve been thinking about it for a long time, and I know that I would regret it if I didn’t try to do that.</p> <p>Keep your fingers crossed!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Power of ignorance, or how to write simple code]]>https://event-driven.io/en/power_of_ignorance/https://event-driven.io/en/power_of_ignorance/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/bf56bbeed5fdd981532779aa000260ba/332ff/2022-05-25-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADYUlEQVQozwFWA6n8ABoXFxENEA4KDBISGCouOkBEUV5gaXZ6hXByfG1xfHmAjJ+su2d6i1Q7ZWFGaldUVEs4MkQyLz0qJiYZFwBARlMeHCQkISc9SmE+UWlUYXN4fYeOmKObp7s5UGdEXHYgNVkXM1tQYHdMXGJMUFVHPkE/LCY4JiEoGhgAL0VhICc0NkBVNVFxIUZgKjE5JyInJBkaYGBlOlhgO1tnHjFRJTpalmlWbUpBZGVsVEVBOichOCcjNyknABgeLBccKVhjeVZxjFJ1ky8+UVwuIaBWPSMTDw4eNhAbNBMpSzVAWLt/Z6RhRlxOUVtfbEMzMkI7QkZFUgAlLkBTXGuXoq6OnKt5kahjg6B/XVPUgmJvOSdPWWZYeJpQZH5YU1qpZU17QCtJLCJbY3N1c3p8cG9oXFwAjp6vna/Amq/Cma/ElrHKl67DdXFumVtDmVpDd2dcgIqUX2l3RT9FXEU/WkQ6aFtSgHRtrrG36PT+3ebvAOXv8+jx8+ry9Oz1+MDBwaaXh1pNRH1nWY15a5aMgqOZkmFbXzk2PGBaW4R3bJSFd5CCd3prYq2vstfm9QDb49/Z4d7e6efOz8m2oYl6Y05WSUKPhHeajH2ViHuNg3x0bnAxKy5XTk9qX190Z2BYSkVdTkd5a2OcrMMAv8nK4vDvw8rJeGthYlFCZFhPbmBVjH5ynY6Ac2ZdhHp1hX59ODI0MystWlJTYVhXRTo5OS0pV0c+eYymAKWfnsfDvo2Ff0oyKWlWULKtrIZ6cZCFfZuQhop+dLWlk6KYkDkyNj43OlZPUlhRU0M4OVJCQEIyPUA9UQBJMyyKdWaggG5cVld9bmx5YVWxoI26rJuLgHeYhnXAq5GTh3xDOzw/ODlMREdGPEEvHSk/KjxBLD9DLj8AcTosl3Vjx5t/RUBTP01qJwsDknVh3sOnn4x8rJmClYRybWdoJSUuIB8nT1JYOTRCMyAyPCo9QS5ASDVGAItKNppoUGNURXA0IoFJOo5ZSO27nI91Zkg1L2FUTF9ZWUJNYRYmPw8gQiw1RmlJSndISnVJS3lNT35SUwBUQTiFcldmV0RrPC6BWUV/bViOZ1JbSEAtKCgeGBsXEhQNDhcFDioAEzggITixaly7b2C1bWC1c2WzcWRKmUfrU/tmcwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/bf56bbeed5fdd981532779aa000260ba/a331c/2022-05-25-cover.png" srcset="/static/bf56bbeed5fdd981532779aa000260ba/36ca5/2022-05-25-cover.png 200w, /static/bf56bbeed5fdd981532779aa000260ba/a3397/2022-05-25-cover.png 400w, /static/bf56bbeed5fdd981532779aa000260ba/a331c/2022-05-25-cover.png 800w, /static/bf56bbeed5fdd981532779aa000260ba/332ff/2022-05-25-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I was asked to compile various statistics from our Event Store GitHub community some time ago. We wanted to analyze our public repositories in terms of, for example:</p> <ul> <li>summary data: number of stars, pull request, forks, etc.</li> <li>but also data over time, i.e. how did they increase over time,</li> <li>additionally, data such as issues, number of discussions, comments, etc.</li> </ul> <p>The Event Store has a lot of repositories (we’re busy bees), so trying to copy and paste data from GitHub UI into Excel would be pointless. Well, we are programmers for something; we automate. GitHub provides its API (<a href="https://docs.github.com/en/rest">https://docs.github.com/en/rest</a>) to get the whole spectrum of information. I decided to go on this path. Still, having API is one thing, and scraping data efficiently, is another.</p> <p>How to do it then? The top results will suggest Python if you google “data scraping”. Probably you don’t even need to google that. Python and data scraping come hand in hand.</p> <p>And here comes my ignorance, which turned out to be my advantage. I don’t know much about Python. Before this task, I only wrote a small, simple thing to integrate data from two APIs overnight. It was a bit similar, and I had pleasant memories, so I said: <em>“Why not? Let’s go deeper”</em>. It turned out that Python is a straightforward and intuitive language. Even such an ignorant like me was able to write a program that scraped a significant amount of data from GitHub and analyzed it. Python is a dynamic programming language. Because of that, it allows rapid prototyping and reduces unnecessary code structures. Plus, it is very expressive and straightforward (as I already noted).</p> <p>Additionally, it has ingeniously simple support for data processing and analysis. <a href="https://pandas.pydata.org/">Pandas</a> allows you to use code like Excel. Really!</p> <p>C# and Java developers sometimes feel superior and laugh at some languages ​​or technologies. Yet, they ignore the usage context. Each programming language is a tool. If the tool is used for the wrong job, it may have a harmful effect. But if we’re using it in the proper context benefiting from the strong sides, it may turn out that the laughter is actually ignorance - the damaging ignorance.</p> <p>The positive ignorance, in my case, resulted in being humble. I didn’t know Python best practices too much, so I didn’t try to overengineer it. I focused on getting the job done and using the simple solutions and tools that could help me to get it right. No layers there, just simple methods with no crazy structures or optimization. Even though I have 15 years of experience in C#, I’m sure that I wouldn’t be able to get this job done in a similar time as I was able to do as a Python noob.</p> <p>I have recently been discussing a bit with my sister, who is taking her first steps in IT. It turns out that explaining to a beginner why a given code is crap and the other is great is not easy. I could just say <em>“it depends”</em>, but such an answer will not explain anything to a beginner. For instance, when I was thinking about describing why CQRS is better than Spaghetti Code or why it is more straightforward, I realized how many things we’re taking for granted. Having learned something, we often forget our journey and how we come to our current conclusions.</p> <p>Okay, so what’s the moral of that? Let’s write simple code. How to do this? Using the right tools for the job and not trying to use all of the most sublime structures. Focusing on getting our code to do its job the best it can. Nothing more, nothing less.</p> <p>There is such a thing as “elevator pitch” in startup nomenclature. If you accidentally find your dream investor in an elevator, you should be able to present your idea to him during the ride. If you cannot explain it and advertise it within this time frame, maybe your idea is not good enough or does not have noticeable direct benefits.</p> <p>I propose to verify that our code and architecture will pass the <em>“junior developer pitch”</em>. So will you be able to describe in short, simple words how it works and why it was designed like that?</p> <p>If you want to see the code I created, I open-sourced it. You can find it here: <a href="https://github.com/oskardudycz/community-stats">https://github.com/oskardudycz/community-stats</a>. I take Pull Requests!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[What onion has to do with Clean Code?]]>https://event-driven.io/en/onion_clean_code/https://event-driven.io/en/onion_clean_code/<p>I quite often make fun of Clean Code and Clean Architecture. I’m calling it an Onion Architecture. Not least because it has layers but also because it’s smelly. I have already told about it several times in the blog and talks, but it is never enough to emphasize it!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3bca633696495493d798a6132a5e5cc4/332ff/2022-05-18-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAAC90lEQVQozx2RWVPbVgBG9Zvz2rfQ6RAwGGNaDAkkwSwmNY0XHBsXA943ebckY8lXkrVZV1dXq1VoXzrk4XxvZ+abOcTKsC3b94NXd/1i+y9u8OIFrzpyoeVD7EPTxfbatH3TWdv+q+UGyPR004XIsd01sTIcWUMcEIEgc7wMREUU5dtUOnF+VbgrJVOZ1G0+k7srFEssJ6i6Kci6rCHNsHVkEyvksbwEAI+QiQ1N1dT5lOpdXwwSn2epxDD1Z//qrHf5eTYe8YLICwLP8xNqpkBH0S0C2QEQFUmSLIzqk+V1Q/3SXH5tyX+1V4NnyTF0DKGprzDG2LLeBmOWA7rprZBLmE4gSJrAg8T9aLc8jS4fN+fXm/PrA6HxIdfPVkYWNhG2EDLNN0z8U4bYNyyfMKxAVtTM02gjW4l56QsQz3zdSl1un+lXh+aP/fKCpHlDBSz7vFgAAADHcRQ9+yl7BHZfZnPuqDCLwdo3MYH296XTsPR7CMSiJ/rl7riSeGSpUevx6eHpqVwul7vdTn84htYaYo/w//lvxPChdCeKc5WbA/glKllJDSZXe6Fc5eO2nD8pMgP+udPrkd0u2evLsrwAoukE2A0IL/h3QC9CqU7U/lHOHOpHYVqM8+AcR/eytU9hpXhSmgs8bxgGQoZhQAghywHkrJG9fks1mTLHBWZzkI40Isu9MDzYQZGdcXRnoxHaapXO7ifnVOKGLdBkt3l8zBSL8wUva1jVbQJiX5KVbKn1/qa2MTiNTGP5h4/Z6qdt+nBrkgzlxp0hpSNNVjR1xjXj8Xm1yvEiND1k+YRuegtBWqlSusrs3tO7zMNv4NsHMbU3rYXvmL/blI0NrFq27Gg0aMbjXL3O8aKOXGT7hGa4FMNKkrQUQbHW/yPZfPdr7Jfwxeltv9zqrRR5xs+PevGLwfdJvV6KREb5PMMCRbc0w3m7vVRgfzjtj+jRmGq329VGp1JvkSRJM7PhhCIH41q/Ux+QZLfXqdTIZvuZ4yH2ddP7HzzerqsQnUEEAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/3bca633696495493d798a6132a5e5cc4/a331c/2022-05-18-cover.png" srcset="/static/3bca633696495493d798a6132a5e5cc4/36ca5/2022-05-18-cover.png 200w, /static/3bca633696495493d798a6132a5e5cc4/a3397/2022-05-18-cover.png 400w, /static/3bca633696495493d798a6132a5e5cc4/a331c/2022-05-18-cover.png 800w, /static/3bca633696495493d798a6132a5e5cc4/332ff/2022-05-18-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Even looking at the picture above, it is difficult for me to understand what the author meant and what the flow should look like. If the idea behind “Clean Code” is to create something maintainable and precise, it goes against what you see in the picture.</p> <p>The general problem is that this tiered breakdown forces you to focus on the technical split rather than the business functionality. Instead of reproducing the business flow, we cut it into separate, distinct pieces. It often becomes challenging to keep track of what is going on from end to end. Even for a simple business operation. API, Services, Repositories, Ports, Adapters, Interfaces, ORMs, etc.</p> <p>In one of my past projects, I counted in the application layers. The result was seven layers to perform a simple CRUD operation. The cognitive load needed to understand such flow and the inertia is so big that it demotivates to make changes. Development costs are rising, and understanding within the team is low.</p> <p>Another important aspect is that this leads to a high risk of change. A change in one layer can potentially affect most functionalities as all mechanisms are shared horizontally. It leads to procrastination, meetings <em>“Can I do it?”</em>, <em>“Are you sure?”</em>, <em>” or maybe do it otherwise?”</em>. It often results in the neglect of necessary changes due to the high risk involved.</p> <p>Code becomes problematic to maintain and understand. So (in my experience), it always ends opposite the initial <em>clean code</em> premise. Coupling is okay if you couple the things that change together, e.g. end-to-end business flow.</p> <p>Therefore, the more layers, the worse the smell. Onion, you remember, right? For me, the definition of <em>clean code</em> is that when I look at it, I think, <em>“wow, that’s so simple. Why I didn’t make it up myself?”</em>. The simple code makes it easy to maintain as it is easy to understand.</p> <p>I am also the one to blame because I made such multi-layered applications. My personal record is a class with 14 generic parameters (<em>sic!</em>). Currently, I prefer to focus on the composition of smaller pieces and cut the application into vertical slices according to the CQRS rules. The simpler and more self-documenting, the better.</p> <p>Of course, writing simple code is not that easy. It takes time, patience and iteration. But it’s worth the effort!</p> <p>Do you want to know more? Read my articles:</p> <ul> <li><a href="/en/generic_does_not_mean_simple/">Generic does not mean Simple</a>,</li> <li><a href="/en/cqrs_facts_and_myths_explained/">CQRS facts and myths explained</a>,</li> <li><a href="/en/how_to_slice_the_codebase_effectively/">How to slice the codebase effectively?</a>.</li> </ul> <p>For example, Dan North created a CUPID policy: <a href="https://dotnetrocks.com/?show=1745">https://dotnetrocks.com/?show=1745</a>.</p> <p>I am also not a fan of Robert C. Martin, in general. But that is a topic for a different article.</p> <p>Do you want more? Watch:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/eOPlg-eB4As?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[A few tricks on how to set up related Docker images with docker-compose]]>https://event-driven.io/en/tricks_on_how_to_set_up_related_docker_images/https://event-driven.io/en/tricks_on_how_to_set_up_related_docker_images/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/67fd4c42f3de35627317b5d2fe97ce92/332ff/2022-05-11-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAAB0ElEQVQoz6VTXU8aQRTd39R3f0X/RP9DbfrU+GaiMRUfWoU0IkqhrcqiaEFjt8rH7oLAbmWXJbCsCH7wscAyO3OnYbeoaWui8SSTzNzJPffOPWcY+gww/0TgyckA4zUY4c4QAZDbINyjAoD/JBOg2LlYFpsv3vhruk4pxRi7CQSAEHBBnJ17uqtsWZZWqW5ED/d56ZYf2bZL4WIwGP5VnAFKuyO8d5QO7vMBlvt+wLltnymqPxQ+PD5O8kJV1zPZ3JcIm5dkMV/IFoqyUrpoNpmMpL5cYH0RjuUK0WRp1uMzez2z3w9vR76y0dnFpRNeeP1upmacf1j1L/k+eVa8/lBoa3cXIcS0rm4SyaxWNfY40Rtk3y6uCcr4zRlRPEmlKmXlB3ekldVUKikImZJcZGOxvCQbjca47Zt252c6y+eKq1sHnzdjr+YC017WRtZpl6omKCZIXdAtqvagPADZhI59N3jmonX5fjngXf82/3FtbiW4vRM/rxs20Ny1zTXtaB3FG3izhuINe0dH69rI6GNHIGfa7U5Xq9bzhV+J0/LUfEI4q7h3Y5EnpiHwCIdVWm2zP/zjGQdk4hOYsNwXi3GkB4wJIeQhG8Kjvf2Ej/EbwnEHnPkn8c0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/67fd4c42f3de35627317b5d2fe97ce92/a331c/2022-05-11-cover.png" srcset="/static/67fd4c42f3de35627317b5d2fe97ce92/36ca5/2022-05-11-cover.png 200w, /static/67fd4c42f3de35627317b5d2fe97ce92/a3397/2022-05-11-cover.png 400w, /static/67fd4c42f3de35627317b5d2fe97ce92/a331c/2022-05-11-cover.png 800w, /static/67fd4c42f3de35627317b5d2fe97ce92/332ff/2022-05-11-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>In the last few days, I have been working on guidance on piping EventStoreDB logs into Elasticsearch.</strong> ELK stack (so Elasticsearch, Logstash and Kibana) is one of the most popular tools for ingesting and analyzing logs and statistics. There are plenty of ways to configure it, but I wanted to do it idiomatically as I was doing the initial guide.</p> <p>I wanted to provide and test a few scenarios with the following tools:</p> <ul> <li><a href="https://developers.eventstore.com/">EventStoreDB</a> - source for logs</li> <li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/8.2/index.html">Elasticsearch</a> to store logs. It’s a document database built on top of the <a href="https://lucene.apache.org/">Lucene</a> full-text search engine.</li> <li><a href="https://www.elastic.co/guide/en/kibana/8.2/index.html">Kibana</a> - UI for browsing logs with dashboards and visualizations.</li> <li><a href="https://www.elastic.co/guide/en/logstash/current/getting-started-with-logstash.html">Logstash</a> - tool for gathering and/or transforming log files to Elasticsearch</li> <li><a href="https://www.elastic.co/guide/en/beats/filebeat/8.2/index.html">Filebeat</a>: the latest tool that offloaded Logstash from tailing files efficiently.</li> </ul> <p><strong>I knew that I’ll have to test those tools in multiple environments, as EventStoreDB can be run both on Windows and Linux.</strong> Also, as I wasn’t the ELK stack expert, I expected to require a few iterations before making it right. I didn’t want to end up reinstalling my dev machine. Although it’s not recommended to set up EventStoreDB or ELK stack on top of the Docker engine, I decided to use this approach as:</p> <ul> <li>this would allow me to quickly ephemeral environments,</li> <li>I didn’t need to provide the entire production setup but rather show how configuration files with the EventStoreDB specifics should look. I could reference the detailed installation instructions to EventStoreDB and ELK stack documentation. Especially, adding too many non-related details would blur the guidance.</li> <li>I wanted to have a repeatable configuration that someone could just take and play with it without going through all the installation steps.</li> <li>I had previous experience with various Docker setups.</li> </ul> <p><strong>Of course, it didn’t go as smoothly as I had imagined.</strong> Windows 11 and Linux definitely have distinct sets of quirks to align. For instance, even though containers should behave the same way between operating systems, it’s not always like that, especially if that involves sharing files, network config etc., with the host operating system.</p> <p><strong>Let’s start with file sharing between containers.</strong> I had to shovel the EventStoreDB log file with Filebeat. I could also provide my Dockerfile using the EventStoreDB image as the base one and install Filebeat in it. It would be closer to the production usage, but this would force me to maintain my Dockerfile. It is far from ideal, making different testing combinations of releases harder. Yet, the purpose of my samples was to show how to configure Filebeats to grab EventStoreDB log files. For that, it’s enough to have access to files. I decided to use separate, unchanged Docker images maintained by EventStoreDB and Elastic and map the same files source.</p> <p>Docker has the concept of <a href="https://docs.docker.com/storage/volumes/">volumes</a>. It enables storing the container data. You can restart the container, and the data will remains. It also allows to mount/bind the host operating system files to the container. It can go both ways, you can send files to the container, but you can also see generated files from the container in the host storage. Of course, if you do it right…</p> <p>There are plenty of ways how to configure volumes. Let’s focus on the most straightforward one that I used. Docker-compose in the latest versions allows defining templates that can be shared between docker image configurations. We could use it to describe the volume that we’ll share between EventStoreDB and Filebeat containers. What’s more, we’ll also get those files in our host system to browse the logs easily and debug/diagnose potential issues. It can look as follows:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token comment"># map logs to disk location and share between containers</span> <span class="token key atrule">x-eventstore-volume-logs</span><span class="token punctuation">:</span> <span class="token important">&amp;eventstore-volume-logs</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> bind <span class="token key atrule">source</span><span class="token punctuation">:</span> ./logs <span class="token key atrule">target</span><span class="token punctuation">:</span> /var/log/eventstore <span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3.8"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token comment">#######################################################</span> <span class="token comment"># EventStoreDB</span> <span class="token comment">#######################################################</span> <span class="token key atrule">eventstoredb</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> eventstore/eventstore<span class="token punctuation">:</span>21.10.2<span class="token punctuation">-</span>buster<span class="token punctuation">-</span>slim <span class="token key atrule">container_name</span><span class="token punctuation">:</span> eventstoredb <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token comment"># external volume to be able to share EventStoreDB logs</span> <span class="token comment"># with Filebeat image</span> <span class="token punctuation">-</span> <span class="token key atrule">&lt;&lt;</span><span class="token punctuation">:</span> <span class="token important">*eventstore-volume-logs</span> <span class="token comment">#######################################################</span> <span class="token comment"># Filebeat to harvest logs from event store logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">filebeat</span><span class="token punctuation">:</span> <span class="token comment"># required to have a proper access to config file on Windows</span> <span class="token key atrule">entrypoint</span><span class="token punctuation">:</span> <span class="token string">"filebeat -e -strict.perms=false"</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/beats/filebeat<span class="token punctuation">:</span>8.2.0 <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token comment"># add Filebeat config file</span> <span class="token punctuation">-</span> <span class="token string">"./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro"</span> <span class="token comment"># get access to EventStoreDB logs through shared external volume</span> <span class="token punctuation">-</span> <span class="token key atrule">&lt;&lt;</span><span class="token punctuation">:</span> <span class="token important">*eventstore-volume-logs</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> eventstoredb</code></pre></div> <p>First, we’re defining the template, telling that we’d like to bind host storage <em>./logs</em> folder (relative to the <em>docker-compose.yml</em> file location) to the <em>/var/log/eventstore</em> directory inside the container. That’s the default log location in the EventStoreDB docker image. We’re using this template both in the EventStoreDB and Filebeat image.</p> <p>Thanks to that, the EventStoreDB container will generate the logs and bind them to the host storage, which will also be mapped into the Filebeat container (as both containers share the exact file location).</p> <p><strong>There’s one more quirk into that. Docker won’t always create <em>./logs</em> folder automatically.</strong> It may not have needed permissions. If we want to have it ready without running on elevated permissions, we need to set it up. How to do that without additional shell scripts or manual steps? Especially keeping in mind that in Git we cannot create empty folders? We can set up a <em>logs</em> folder with an empty <em>.gitignore</em> file. Thanks to that, if we clone the fresh Git repository, the folder will be already created.</p> <p>We need to tell Filebeat where it should search for log files. It can be done using <em>filebeat.yml</em>. We’ll bind it from the host file system. So we can easily modify it in our IDE. We have to also set up proper permissions inside the container. We could do it by adding <em>:ro</em> suffix in the volume definition. We don’t need to use template here, as it’s a regular volume.</p> <p>The file can look as follows.</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token comment">#######################################################</span> <span class="token comment"># EventStoreDB logs file input</span> <span class="token comment">#######################################################</span> <span class="token key atrule">filebeat.inputs</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> log <span class="token key atrule">paths</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> /var/log/eventstore/<span class="token important">*/log*.json</span> <span class="token key atrule">json.keys_under_root</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token key atrule">json.add_error_key</span><span class="token punctuation">:</span> <span class="token boolean important">true</span> <span class="token comment">#######################################################</span> <span class="token comment"># Logstash output to transform and prepare logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">output.logstash</span><span class="token punctuation">:</span> <span class="token key atrule">hosts</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"logstash:5044"</span><span class="token punctuation">]</span></code></pre></div> <p>In the <em>filebeat.inputs</em> we’re telling where EventStoreDB logs are stored, so in the location defined in our template <em>/var/log/eventstore/</em>. As an output, we’re using a Logstash image. Even though Filebeat can pipe logs directly to Elasticsearch and do a basic Kibana setup, you’d like to have more control and expand the processing pipeline. That’s why for production, it’s recommended to use both. Multiple Filebeat instances (e.g. from different EventStoreDB clusters) can collect logs and pipe them to Logstash, which will play an aggregator role. Filebeat can output logs to Logstash, and Logstash can receive and process these logs with the Beats input. Logstash can transform and route logs to Elasticsearch instance(s).</p> <p><strong>Even from this description, it’s visible that there are dependencies between containers. How to solve the race conditions in a startup?</strong> By convention, internally, Docker images <a href="https://docs.docker.com/engine/reference/builder/#healthcheck">should define healtcheck</a>. By having that, the Docker runtime will know if the container is running. We can also define our checks in docker-compose. We can also set a startup order using <a href="https://docs.docker.com/compose/startup-order/">depends_on</a>. Still, Compose will only know if the container is running, which is not precisely the same as it’s ready. We will, of course, use it, but only for the basic setup. For instance:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token comment">#######################################################</span> <span class="token comment"># Kibana to browse logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">kibana</span><span class="token punctuation">:</span> <span class="token key atrule">container_name</span><span class="token punctuation">:</span> kibana <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/kibana/kibana<span class="token punctuation">:</span>8.2.0 <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ELASTICSEARCH_HOSTS=http<span class="token punctuation">:</span>//elasticsearch<span class="token punctuation">:</span><span class="token number">9200</span> <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5601:5601"</span> <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> es_network <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> elasticsearch <span class="token key atrule">healthcheck</span><span class="token punctuation">:</span> <span class="token key atrule">test</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token string">"CMD-SHELL"</span><span class="token punctuation">,</span> <span class="token string">"curl --fail http://localhost:5601 || exit 1"</span><span class="token punctuation">,</span> <span class="token punctuation">]</span> <span class="token key atrule">interval</span><span class="token punctuation">:</span> 10s <span class="token key atrule">timeout</span><span class="token punctuation">:</span> 10s <span class="token key atrule">retries</span><span class="token punctuation">:</span> <span class="token number">120</span></code></pre></div> <p>It will depend on the Elasticsearch image, and it’ll also have a custom health check to verify if Kibana is not only running but also ready. It will also always restart unless stopped manually by us.</p> <p><strong>The rest we’ll need to do orchestration manually.</strong> For instance, we’d like to automatically create a Kibana data view. As Kibana exposes an HTTP API, we’ll use the <a href="https://curl.se/">curl</a> image for that.</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token comment">#######################################################</span> <span class="token comment"># Call curl once Kibana was started </span> <span class="token comment"># to create Kibana Data Views for ESDB logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">initializer</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> curlimages/curl <span class="token key atrule">restart</span><span class="token punctuation">:</span> on<span class="token punctuation">-</span>failure <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> kibana <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> es_network <span class="token key atrule">command</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"sh"</span><span class="token punctuation">,</span><span class="token string">"-c"</span><span class="token punctuation">,</span><span class="token string">"sleep 1 &amp;&amp; curl --fail -X POST 'kibana:5601/api/index_patterns/index_pattern' -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d'{ \"index_pattern\": { \"title\": \"eventstoredb-stats\" } }' &amp;&amp; curl --fail -X POST 'kibana:5601/api/index_patterns/index_pattern' -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d'{ \"index_pattern\": { \"title\": \"eventstoredb-logs\" } }' || exit 1"</span><span class="token punctuation">]</span></code></pre></div> <p>This image will depend on Kibana, and once it’s started, it will try to make the HTTP requests to set up two data views (index patterns), one for logs and the other for stats. Thanks to that, we won’t need to do it manually through UI. If any of the HTTP requests fail, it’ll restart the container. Then wait one second and try again. Once Kibana is ready and requests succeeded, it will finish with status code <em>1</em>. It’ll tell Docker that the run succeeded and won’t restart the container again. So no more requests will be made.</p> <p>The full docker-compose configuration will look as follows:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token comment">###########################################################################</span> <span class="token comment"># Storing EventStoreDB logs into ElasticSearch with Filebeat and Logstash</span> <span class="token comment">###########################################################################</span> <span class="token comment"># DISCLAIMER: This configuration is presented as docker-compose</span> <span class="token comment"># to simplify the developer environment setup. </span> <span class="token comment"># It aims to give the quick option to play with Elastic setup.</span> <span class="token comment"># It's NOT recommended to run setup through docker-compose on production.</span> <span class="token comment">###########################################################################</span> <span class="token comment"># map logs to disk location and share between containers</span> <span class="token key atrule">x-eventstore-volume-logs</span><span class="token punctuation">:</span> <span class="token important">&amp;eventstore-volume-logs</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> bind <span class="token key atrule">source</span><span class="token punctuation">:</span> ./logs <span class="token key atrule">target</span><span class="token punctuation">:</span> /var/log/eventstore <span class="token key atrule">version</span><span class="token punctuation">:</span> <span class="token string">"3.8"</span> <span class="token key atrule">services</span><span class="token punctuation">:</span> <span class="token comment">#######################################################</span> <span class="token comment"># EventStoreDB</span> <span class="token comment">#######################################################</span> <span class="token key atrule">eventstoredb</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> eventstore/eventstore<span class="token punctuation">:</span>21.10.2<span class="token punctuation">-</span>buster<span class="token punctuation">-</span>slim <span class="token key atrule">container_name</span><span class="token punctuation">:</span> eventstoredb <span class="token comment"># use this image if you're running ARM-based proc like Apple M1</span> <span class="token comment"># image: ghcr.io/eventstore/eventstore:21.10.0-alpha-arm64v8</span> <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> EVENTSTORE_CLUSTER_SIZE=1 <span class="token punctuation">-</span> EVENTSTORE_RUN_PROJECTIONS=All <span class="token punctuation">-</span> EVENTSTORE_START_STANDARD_PROJECTIONS=true <span class="token punctuation">-</span> EVENTSTORE_EXT_TCP_PORT=1113 <span class="token punctuation">-</span> EVENTSTORE_HTTP_PORT=2113 <span class="token punctuation">-</span> EVENTSTORE_INSECURE=true <span class="token punctuation">-</span> EVENTSTORE_ENABLE_EXTERNAL_TCP=true <span class="token punctuation">-</span> EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">'1113:1113'</span> <span class="token punctuation">-</span> <span class="token string">'2113:2113'</span> <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">type</span><span class="token punctuation">:</span> volume <span class="token key atrule">source</span><span class="token punctuation">:</span> eventstore<span class="token punctuation">-</span>volume<span class="token punctuation">-</span>data <span class="token key atrule">target</span><span class="token punctuation">:</span> /var/lib/eventstore <span class="token comment"># external volume to be able to share EventStoreDB logs</span> <span class="token comment"># with Filebeat image</span> <span class="token punctuation">-</span> <span class="token key atrule">&lt;&lt;</span><span class="token punctuation">:</span> <span class="token important">*eventstore-volume-logs</span> <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> esdb_network <span class="token comment">#######################################################</span> <span class="token comment"># Filebeat to harvest logs from event store logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">filebeat</span><span class="token punctuation">:</span> <span class="token comment"># required to have a proper access to config file on Windows</span> <span class="token key atrule">entrypoint</span><span class="token punctuation">:</span> <span class="token string">"filebeat -e -strict.perms=false"</span> <span class="token key atrule">container_name</span><span class="token punctuation">:</span> file_beat <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/beats/filebeat<span class="token punctuation">:</span>8.2.0 <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> es_network <span class="token comment"># make sure that Filebeat is restarted</span> <span class="token comment"># in case ElasticSearch or EventStoreDB</span> <span class="token comment"># were not available yet</span> <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token comment"># add Filebeat config file</span> <span class="token punctuation">-</span> <span class="token string">"./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro"</span> <span class="token comment"># get access to EventStoreDB logs through shared external volume</span> <span class="token punctuation">-</span> <span class="token key atrule">&lt;&lt;</span><span class="token punctuation">:</span> <span class="token important">*eventstore-volume-logs</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> eventstoredb <span class="token punctuation">-</span> logstash <span class="token comment">#######################################################</span> <span class="token comment"># Logstash for more advanced log pipelines</span> <span class="token comment"># e.g. filtering, transformations, etc.</span> <span class="token comment"># this will split stats and regular logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">logstash</span><span class="token punctuation">:</span> <span class="token comment"># required to have a proper access to config file on windows</span> <span class="token key atrule">container_name</span><span class="token punctuation">:</span> logstash <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/logstash/logstash<span class="token punctuation">:</span>8.2.0 <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> es_network <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token comment"># add Logstash config file</span> <span class="token punctuation">-</span> <span class="token string">"./logstash.conf:/usr/share/logstash/pipeline/logstash.conf:ro"</span> <span class="token comment"># make sure that LogStash is restarted</span> <span class="token comment"># in case ElasticSearch was not available yet</span> <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> elasticsearch <span class="token comment">#######################################################</span> <span class="token comment"># Elastic Search to store logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">elasticsearch</span><span class="token punctuation">:</span> <span class="token key atrule">container_name</span><span class="token punctuation">:</span> elasticsearch <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/elasticsearch/elasticsearch<span class="token punctuation">:</span>8.2.0 <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> discovery.type=single<span class="token punctuation">-</span>node <span class="token punctuation">-</span> xpack.security.enabled=false <span class="token punctuation">-</span> bootstrap.memory_lock=true <span class="token punctuation">-</span> <span class="token string">"ES_JAVA_OPTS=-Xms512m -Xmx512m"</span> <span class="token key atrule">ulimits</span><span class="token punctuation">:</span> <span class="token key atrule">memlock</span><span class="token punctuation">:</span> <span class="token key atrule">soft</span><span class="token punctuation">:</span> <span class="token number">-1</span> <span class="token key atrule">hard</span><span class="token punctuation">:</span> <span class="token number">-1</span> <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> elastic<span class="token punctuation">-</span>data<span class="token punctuation">:</span>/usr/share/elasticsearch/data <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"9200:9200"</span> <span class="token punctuation">-</span> <span class="token string">"9300:9300"</span> <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> es_network <span class="token key atrule">healthcheck</span><span class="token punctuation">:</span> <span class="token key atrule">test</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token string">"CMD-SHELL"</span><span class="token punctuation">,</span> <span class="token string">"curl --fail http://localhost:9200 || exit 1"</span><span class="token punctuation">,</span> <span class="token punctuation">]</span> <span class="token key atrule">interval</span><span class="token punctuation">:</span> 10s <span class="token key atrule">timeout</span><span class="token punctuation">:</span> 10s <span class="token key atrule">retries</span><span class="token punctuation">:</span> <span class="token number">120</span> <span class="token comment">#######################################################</span> <span class="token comment"># Kibana to browse logs</span> <span class="token comment"># Filebeat will automatically create Data View for logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">kibana</span><span class="token punctuation">:</span> <span class="token key atrule">container_name</span><span class="token punctuation">:</span> kibana <span class="token key atrule">image</span><span class="token punctuation">:</span> docker.elastic.co/kibana/kibana<span class="token punctuation">:</span>8.2.0 <span class="token key atrule">environment</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> ELASTICSEARCH_HOSTS=http<span class="token punctuation">:</span>//elasticsearch<span class="token punctuation">:</span><span class="token number">9200</span> <span class="token key atrule">restart</span><span class="token punctuation">:</span> unless<span class="token punctuation">-</span>stopped <span class="token key atrule">ports</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token string">"5601:5601"</span> <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> es_network <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> elasticsearch <span class="token key atrule">healthcheck</span><span class="token punctuation">:</span> <span class="token key atrule">test</span><span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token string">"CMD-SHELL"</span><span class="token punctuation">,</span> <span class="token string">"curl --fail http://localhost:5601 || exit 1"</span><span class="token punctuation">,</span> <span class="token punctuation">]</span> <span class="token key atrule">interval</span><span class="token punctuation">:</span> 10s <span class="token key atrule">timeout</span><span class="token punctuation">:</span> 10s <span class="token key atrule">retries</span><span class="token punctuation">:</span> <span class="token number">120</span> <span class="token comment">#######################################################</span> <span class="token comment"># Call curl once Kibana was started </span> <span class="token comment"># to create Kibana Data Views for ESDB logs</span> <span class="token comment">#######################################################</span> <span class="token key atrule">initializer</span><span class="token punctuation">:</span> <span class="token key atrule">image</span><span class="token punctuation">:</span> curlimages/curl <span class="token key atrule">restart</span><span class="token punctuation">:</span> on<span class="token punctuation">-</span>failure <span class="token key atrule">depends_on</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> kibana <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> es_network <span class="token key atrule">command</span><span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">"sh"</span><span class="token punctuation">,</span><span class="token string">"-c"</span><span class="token punctuation">,</span><span class="token string">"sleep 1 &amp;&amp; curl --fail -X POST 'kibana:5601/api/index_patterns/index_pattern' -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d'{ \"index_pattern\": { \"title\": \"eventstoredb-stats\" } }' &amp;&amp; curl --fail -X POST 'kibana:5601/api/index_patterns/index_pattern' -H 'kbn-xsrf: true' -H 'Content-Type: application/json' -d'{ \"index_pattern\": { \"title\": \"eventstoredb-logs\" } }' || exit 1"</span><span class="token punctuation">]</span> <span class="token key atrule">networks</span><span class="token punctuation">:</span> <span class="token key atrule">es_network</span><span class="token punctuation">:</span> <span class="token key atrule">driver</span><span class="token punctuation">:</span> bridge <span class="token key atrule">esdb_network</span><span class="token punctuation">:</span> <span class="token key atrule">driver</span><span class="token punctuation">:</span> bridge <span class="token key atrule">volumes</span><span class="token punctuation">:</span> <span class="token key atrule">eventstore-volume-data</span><span class="token punctuation">:</span> elastic<span class="token punctuation">-</span>data<span class="token punctuation">:</span></code></pre></div> <p>Logstash config file that grabs logs from Filebeat and splits logs and stats into different Elasticsearch indexes looks like:</p> <div class="gatsby-highlight" data-language="ruby"><pre class="language-ruby"><code class="language-ruby"><span class="token comment">#######################################################</span> <span class="token comment"># Filebeat input </span> <span class="token comment">#######################################################</span> input <span class="token punctuation">{</span> beats <span class="token punctuation">{</span> port <span class="token operator">=></span> <span class="token number">5044</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">#######################################################</span> <span class="token comment"># Filter out stats from regular logs</span> <span class="token comment"># add respecting field with log type</span> <span class="token comment">#######################################################</span> filter <span class="token punctuation">{</span> <span class="token comment"># check if log path includes "log-stats"</span> <span class="token comment"># so pattern for stats</span> <span class="token keyword">if</span> <span class="token punctuation">[</span>log<span class="token punctuation">]</span><span class="token punctuation">[</span>file<span class="token punctuation">]</span><span class="token punctuation">[</span>path<span class="token punctuation">]</span> <span class="token operator">=~</span> <span class="token string-literal"><span class="token string">"log-stats"</span></span> <span class="token punctuation">{</span> mutate <span class="token punctuation">{</span> add_field <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token string-literal"><span class="token string">"log_type"</span></span> <span class="token operator">=></span> <span class="token string-literal"><span class="token string">"stats"</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> mutate <span class="token punctuation">{</span> add_field <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token string-literal"><span class="token string">"log_type"</span></span> <span class="token operator">=></span> <span class="token string-literal"><span class="token string">"logs"</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">#######################################################</span> <span class="token comment"># Send logs to Elastic</span> <span class="token comment"># Create separate indexes for stats and regular logs</span> <span class="token comment"># using field defined in the filter transformation</span> <span class="token comment">#######################################################</span> output <span class="token punctuation">{</span> elasticsearch <span class="token punctuation">{</span> hosts <span class="token operator">=></span> <span class="token punctuation">[</span> <span class="token string-literal"><span class="token string">"elasticsearch:9200"</span></span> <span class="token punctuation">]</span> index <span class="token operator">=></span> <span class="token string-literal"><span class="token string">'eventstoredb-%{[log_type]}'</span></span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>See the complete changes in the pull requests to:</p> <ul> <li><a href="https://github.com/EventStore/EventStore/pull/3488">EventStoreDB docs</a>,</li> <li><a href="https://github.com/EventStore/samples/pull/17">EventStoreDB samples</a></li> </ul> <p>I hope that this article will give you enough background for playing with the complex developer environment’s Docker configuration. Docker-compose is a decent tool for setting up temporary environments. They’re great for testing and playing with the ideas and various configurations.</p> <p>Read also other articles around DevOps process:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/docker_compose_profiles/">Docker Compose Profile, one the most useful and underrated features</a></li> <li><a href="/en/marten_and_docker/">How to create a Docker image for the Marten application</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Persistent vs catch-up, EventStoreDB subscriptions in action]]>https://event-driven.io/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/https://event-driven.io/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ded5780b4118ec600d7e02afe9849123/332ff/2022-05-04-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADYUlEQVQozwFWA6n8ABYydAsreDNRkaOqtpSbqoaPpZKZpnJ9m3qCnaCmsZafppSdopehp5KcpY2YpaSstre+x0lOZEcyLmxbYgAyM04uNVc/RmZ3cHZ8dXl4cXdvaG1iXWxxaXGGe3t8bmxuY2dzaGiAdG5vYl6Ac2yHeXNMPz9aQjWJb2IAhFMuiFkzj186jl47jlo3j1k3ilo6i1o5ilc5gFU5TzEib0cwe00tdkgnekkpdEUkbD0cdUclgFQxh10/AEArLkgvNU83PVQ7PWZGQ14+OkguMEQrMTUfJFIxInBBLB8NB0AjGkslFlYnEFYlDFEgC3lAK55cPoZZQABwT0Z1VU6AXlqHY1xLOUN7XFeBWk97VEkmEhVoOSy+bVRCHhcpFA91Si5qPh93OyGWTzSIRSGFUS+WblUAZTggZz8ob0k+fFRIVSsbbDshfkgzXTkqCwQBVTYttV9KPyUbFgwGckEhyzgfy1s/ekwsZDYWdkgmkGA6AFk/NlxDO2pRT11HSF0/Nm9GM4stFpkbE1gLBmIZE4ZHLGkXC3kXDrYdC+ALAJIxIlQ9L0wvJWJEOI9zYwCyloK1mojDqJatlYenkIfFrJ+kSzy5AwDZFwf1GAzSIg7lGgXQFAK/BwCpMCGpin6ni4CjhnqegXWZf3IAvKOOwaaUw6eXyKudy62eza+hzKaYozYqwA0A8SAQ8xYE0RoHtA4Apjwwt5aLu6CVt5qNs5aLsJKHq4+HALqijb+jksKnlsOnlsSnlcOkk8Srmb+jldItHeoZCfQbCdcaB7QdDsGdkcGnm7yckbibkLKXjK2QhqiNhgCqglyrhV2xiWC1iV63jWO3jma0imOwkWzORC/wEAHwGwjSEwHAKhWzimquhWapg2WjgGWdfmSXeF+VdmEAqoBLpnxLp3lMp3lOqHtQpnhNpnhOpXtSuDIf2RME2RMCzxEAuBoIk1w9lGxHlGlGlGxKjmdCiGI+i2VFAINjPqSdlq+sqa6ppaahnL+8urmupK+kma6amKuHhLiHgsW7uZ6JiKSioLGtqrCrqKmkoLKurKykoHpYPQCNa0ibg22eiXWbiHOPe2amk35rYFlaTEmsTkW6RDi6Sj/AXVO0PTOUZFOOgG6Rfm93ZFh/b2OKd2Z5W0XjYmB9WJFP2QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ded5780b4118ec600d7e02afe9849123/a331c/2022-05-04-cover.png" srcset="/static/ded5780b4118ec600d7e02afe9849123/36ca5/2022-05-04-cover.png 200w, /static/ded5780b4118ec600d7e02afe9849123/a3397/2022-05-04-cover.png 400w, /static/ded5780b4118ec600d7e02afe9849123/a331c/2022-05-04-cover.png 800w, /static/ded5780b4118ec600d7e02afe9849123/332ff/2022-05-04-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Events can be a great facilitator and glue for business workflows. Subscriptions are an essential block of the event-driven system. They notify us about each of the recorded events. We can trigger the following steps and ensure they’re always processed based on the recorded events. I wrote already about that <a href="/en/integrating_Marten/">aspect in Marten</a>, today I’d like to focus on <a href="https://developers.eventstore.com">EventStoreDB</a>.</p> <p><strong>EventStoreDB has two types of subscriptions:</strong></p> <ul> <li><a href="https://developers.eventstore.com/clients/grpc/subscriptions.html">Catch-up</a>,</li> <li><a href="https://developers.eventstore.com/server/v21.10/persistent-subscriptions.html">Persistent</a>.</li> </ul> <p><strong>They may look similar, but their use cases are much different.</strong> Persistent subscriptions look tempting. They have a more straightforward API, as it seems that they handle all internally: scaling consumers, handling retries and knowing which event was processed the last time. All of that looks fabulous, right? Holy grail? Well, almost.</p> <p><strong>Persistent subscriptions implement the <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/competing-consumers">Competing Consumers Pattern</a>.</strong> Persistent subscriptions work on a consumer group basis. Do you know how Kafka consumer groups work? Yes? Then EventStoreDB consumer groups are much different. Kafka guarantees that only a single consumer will get an event from a specific partition for the consumer group. You have an ordering guarantee for the events inside a partition, thanks to that.</p> <p><strong>That, of course, has a downside;</strong> Kafka cannot distribute the load for the specific partition. Like, e.g. RabbitMQ is doing. If you have multiple consumers of the particular queue, then RabbitMQ will try immediately distributing messages between them. Together with retries, that may lead to race conditions and out of order processing. For instance, when one consumer is slower than the other, message processing fails with a transient error and has to be processed again. The same applies to EventStoreDB. It will do its best to distribute events to respect events order; however, it will always be <em>just</em> the best effort. Different <a href="https://developers.eventstore.com/server/v21.10/persistent-subscriptions.html#consumer-strategies">consuming strategies</a> can help, but they will change the scale of the issue. <strong>Thus, persistent subscriptions work great if you need <em>broadcast</em>. So you’re notifying multiple consumers that something has happened, but the order is not critical.</strong></p> <p><strong>Where processing order matters, you should use catch-up subscriptions.</strong> They respect the order but are <em>raw</em> compared to persistent subscriptions. They don’t have built-in retries, spreading the load etc. At first glance, that may look bad, but that gives more flexibility and options to fine-tune your use case.</p> <p>You also need to maintain the last processed event position. That means loading and storing checkpoints. The typical flow for catch-up subscriptions looks as follows:</p> <ol> <li>Load the last processed event position.</li> <li>If it exists, subscribe to the next event. If it does not, subscribe from the first event in the stream.</li> <li>Get a notification about the event and process it.</li> <li>Store position of that event.</li> </ol> <p>Thanks to that, when service is down or something goes wrong, you don’t have to start from the beginning, but you know the position. You can also reset the position to reprocess events. Read more about positions in my article <a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>.</p> <p>If you’re storing events in the transactional database, you can store position together with database changes and get exactly-once processing semantics. Fore instance code in <a href="https://github.com/oskardudycz/EventSourcing.NodeJS/pull/19">TypeScript and NodeJS</a> could look like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">getCheckpoints</span> <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">subscriptionCheckpoints</span><span class="token punctuation">(</span><span class="token function">getPostgres</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">loadCheckPointFromPostgres</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>subscriptionId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> checkpoints <span class="token operator">=</span> <span class="token function">getCheckpoints</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> checkpoint <span class="token operator">=</span> <span class="token keyword">await</span> checkpoints<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> id<span class="token operator">:</span> subscriptionId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> checkpoint <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">?</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>checkpoint<span class="token punctuation">.</span>position<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">PostgresEventHandler</span> <span class="token operator">=</span> <span class="token punctuation">(</span> db<span class="token operator">:</span> Transaction<span class="token punctuation">,</span> event<span class="token operator">:</span> SubscriptionResolvedEvent <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token keyword">void</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token function-variable function">storeCheckpointInPostgres</span> <span class="token operator">=</span> <span class="token keyword">async</span> <span class="token punctuation">(</span>event<span class="token operator">:</span> SubscriptionResolvedEvent<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> checkpoints <span class="token operator">=</span> <span class="token function">getCheckpoints</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> checkpoints<span class="token punctuation">.</span><span class="token function">insertOrUpdate</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token string">'id'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> id<span class="token operator">:</span> event<span class="token punctuation">.</span>subscriptionId<span class="token punctuation">,</span> position<span class="token operator">:</span> <span class="token function">Number</span><span class="token punctuation">(</span>event<span class="token punctuation">.</span>commitPosition<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">handleEventInPostgresTransactionScope</span> <span class="token operator">=</span> <span class="token punctuation">(</span>handlers<span class="token operator">:</span> PostgresEventHandler<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">async</span> <span class="token punctuation">(</span>event<span class="token operator">:</span> SubscriptionResolvedEvent<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">getPostgres</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">tx</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>transaction<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> transaction<span class="token punctuation">.</span><span class="token function">task</span><span class="token punctuation">(</span><span class="token keyword">async</span> <span class="token punctuation">(</span>db<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">for</span> <span class="token punctuation">(</span><span class="token keyword">const</span> handle <span class="token keyword">of</span> handlers<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">handle</span><span class="token punctuation">(</span>db<span class="token punctuation">,</span> event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">storeCheckpointInPostgres</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Yet, sometimes you want to keep things simple. For example, you’re using subscriptions for workflow processing (<a href="/en/saga_process_manager_distributed_transactions/">saga, process manager, etc.</a>). Or you’re using a database like MongoDB that doesn’t allow multi-document types transactions. Then you may just want to store events inside EventStoreDB, not to add more moving pieces. How to do that?</p> <p>See the example below <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core.EventStoreDB/Subscriptions/EventStoreDBSubscriptionCheckpointRepository.cs">in C#</a>:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ISubscriptionCheckpointRepository</span> <span class="token punctuation">{</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span><span class="token keyword">ulong</span><span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token function">Load</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> subscriptionId<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token return-type class-name">ValueTask</span> <span class="token function">Store</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> subscriptionId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">ulong</span></span> position<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CheckpointStored</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> SubscriptionId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">ulong</span><span class="token punctuation">?</span></span> Position<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> CheckpointedAt<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventStoreDBSubscriptionCheckpointRepository</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ISubscriptionCheckpointRepository</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">EventStoreClient</span> eventStoreClient<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">EventStoreDBSubscriptionCheckpointRepository</span><span class="token punctuation">(</span> <span class="token class-name">EventStoreClient</span> eventStoreClient<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>eventStoreClient <span class="token operator">=</span> eventStoreClient <span class="token operator">??</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>eventStoreClient<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span><span class="token keyword">ulong</span><span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token function">Load</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> subscriptionId<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> streamName <span class="token operator">=</span> <span class="token function">GetCheckpointStreamName</span><span class="token punctuation">(</span>subscriptionId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> eventStoreClient<span class="token punctuation">.</span><span class="token function">ReadStreamAsync</span><span class="token punctuation">(</span>Direction<span class="token punctuation">.</span>Backwards<span class="token punctuation">,</span> streamName<span class="token punctuation">,</span> StreamPosition<span class="token punctuation">.</span>End<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">await</span> result<span class="token punctuation">.</span>ReadState <span class="token operator">==</span> ReadState<span class="token punctuation">.</span>StreamNotFound<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token class-name">ResolvedEvent<span class="token punctuation">?</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">await</span> result<span class="token punctuation">.</span><span class="token function">FirstOrDefaultAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> @<span class="token keyword">event</span><span class="token punctuation">?.</span><span class="token generic-method"><span class="token function">Deserialize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CheckpointStored<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">?.</span>Position<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">ValueTask</span> <span class="token function">Store</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> subscriptionId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">ulong</span></span> position<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CheckpointStored</span><span class="token punctuation">(</span>subscriptionId<span class="token punctuation">,</span> position<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> eventToAppend <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>@<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token function">ToJsonEventData</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> streamName <span class="token operator">=</span> <span class="token function">GetCheckpointStreamName</span><span class="token punctuation">(</span>subscriptionId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token comment">// store new checkpoint expecting stream to exist</span> <span class="token keyword">await</span> eventStoreClient<span class="token punctuation">.</span><span class="token function">AppendToStreamAsync</span><span class="token punctuation">(</span> streamName<span class="token punctuation">,</span> StreamState<span class="token punctuation">.</span>StreamExists<span class="token punctuation">,</span> eventToAppend<span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">WrongExpectedVersionException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// WrongExpectedVersionException means that stream did not exist</span> <span class="token comment">// Set the checkpoint stream to have at most 1 event</span> <span class="token comment">// using stream metadata $maxCount property</span> <span class="token keyword">await</span> eventStoreClient<span class="token punctuation">.</span><span class="token function">SetStreamMetadataAsync</span><span class="token punctuation">(</span> streamName<span class="token punctuation">,</span> StreamState<span class="token punctuation">.</span>NoStream<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StreamMetadata</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// append event again expecting stream to not exist</span> <span class="token keyword">await</span> eventStoreClient<span class="token punctuation">.</span><span class="token function">AppendToStreamAsync</span><span class="token punctuation">(</span> streamName<span class="token punctuation">,</span> StreamState<span class="token punctuation">.</span>NoStream<span class="token punctuation">,</span> eventToAppend<span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">GetCheckpointStreamName</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> subscriptionId<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token interpolation-string"><span class="token string">$"checkpoint_</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">subscriptionId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Are you doing Java? No worries, here’s how it could look like: <a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/samples/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/subscriptions/EventStoreDBSubscriptionCheckpointRepository.java">EventStoreDBSubscriptionCheckpointRepository.java</a>.</p> <p>This code allows to load and store position in an event stream. Each subscription will get its stream. We need just the last processed position, and there’s no need to keep more than one event. We’re setting <em>$maxCount</em> event data on the stream creation to achieve that. EventStoreDB will ensure that only the latest one event will be kept, and the rest will be <a href="https://developers.eventstore.com/server/v21.10/streams.html#deleting-streams-and-events">truncated</a> (<em>Note: they will be physically removed when the <a href="https://developers.eventstore.com/server/v21.10/operations.html#scavenging-events">scavenging process</a> is run</em>).</p> <p>How to do retries? You can wrap the handling code with a retry policy or implement it case by case. You can use library like <a href="https://github.com/App-vNext/Polly">Polly</a>, or write it as I explained in <a href="/en/long_polling_and_eventual_consistency/">my other article</a>.</p> <p>Scaling? That’s a topic on its own. Luckily, I have an article for you <a href="/en/how_to_scale_projections_in_the_event_driven_systems/">How to scale projections in the event-driven systems?</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to build a simple event pipeline]]>https://event-driven.io/en/how_to_build_simple_event_pipeline/https://event-driven.io/en/how_to_build_simple_event_pipeline/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/89cb7a29c60c5676501b12362360a158/332ff/2022-04-27-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADM0lEQVQozwXBfY/SdgAA4H6DmeW0vLal5aXlpZTSlv5aKLQIpRTwON4OvFI4uIMTh4fi4W1u07ioF+dlW87obmbbGbPoZrYlS7Zlfy1x0a+154Eatlnr5mcru7JtY0wCi4cxCk+oAZLHiRgW5oNai8m2KG2TxVmKAhQewyghGGL9DsQBNW1zMClfXVQn+9WISLv8OIx6KA4nol4Ygdec54MMRvFIgCbj+kC61ERJhxN1wl6H0wNDcVXrWLndeVnIs4F4MMYGsaDXT6JOD3zuw3MOBCZoL0FiDq/HSnOfbW3IaeD2eZyIAyEQiC0YQI6VaqKo0BE2FI779QoXihIfrK25UPiCC8ZpLwsiGIE4PRcoMmx32orZwUl/qRSBqJSMYSji9hh6stvLKZJk1fN2FXTN5LgHUK8Tx9GMSKd42ot6YI/vSqNqVNrZ7VvTfgFKqgW/D8/w8XlfP1pZzx/Ofny8e/Joenx0cHQ4vNwuWe2iaSgSEGi5oIrgmpEBvBivjEeDy1BrvCNJvLmuj6zKjm1sdYslIw/qe7n+raahjnYGY7t3aaMJiuvFrN5Ip5koY1tDVSvhAQraWy33Vh8ZrZooCYLAAVGuDRcZzTDzSqWQ1c1qL51qKSBfKmf4JElFS43ds5//WlxfEQQJNUZDa6df0FUuxQkpRpZ4MhxPMIyomZxWpplEIU7zLEiUt8vrLU5Se9Pbz37598bykAxFIaVY7PXXFV3lNAULBDEEQbxut9vldp6XZUlJK5KcASkRKEYmX00BWdfK33z13fZwHCEpSBSFJOCBmonxPBGkKCpI+FAURV1ud4gMB0m/psiayOYAW7nI10pc3+BfH4zuT9ubVQ3K66oAUkySy8pCuQjKOh+Nkr5AAHa6UD9F86Cra1bdFNKZWklYjLSH1xov9nu/fzn/4cEEOhjUV9PWzSv1R9ebn+w3Z5ZhdXIFk4/G/ElR6U9unj599veff7x8/Wu/09xtXvx8svF8tvXy/uLseAn99uT2Tw9mL+5Ovj4c35lvnt6ZnNydHH9s3bCrnU7/1as3b9/+9+7d+/f/vPn23vzki+Xjg6unny6/P7539uTofycZ68W4MEe6AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/89cb7a29c60c5676501b12362360a158/a331c/2022-04-27-cover.png" srcset="/static/89cb7a29c60c5676501b12362360a158/36ca5/2022-04-27-cover.png 200w, /static/89cb7a29c60c5676501b12362360a158/a3397/2022-04-27-cover.png 400w, /static/89cb7a29c60c5676501b12362360a158/a331c/2022-04-27-cover.png 800w, /static/89cb7a29c60c5676501b12362360a158/332ff/2022-04-27-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Today we will deal with event consumption. I’ll also explain how I wrote the new version of the <a href="https://github.com/jbogard/MediatR">MediatR</a> library. Curious? We’ll get to that later on.</p> <p>If you read my article <a href="/en/integrating_Marten/">Integrating Marten with other systems</a>, you should be already familiar with the concept of the subscriptions. If not, it works like the <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox Pattern</a>. Stored events are then published asynchronously by a background process. We subscribe to incoming events notifications and consume them. This pattern also applies to other event-based systems, e.g .:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core.EventStoreDB/Subscriptions/EventStoreDBSubscriptionToAll.cs">EventStoreDB Subscriptions</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core.Kafka/Consumers/KafkaConsumer.cs">Kafka Consumers</a>.</li> </ul> <p>Okay, but what to do with those events? Typically, we update read models (more about it in <a href="https://event-driven.io/en/how_to_do_events_projections_with_entity_framework/">How to build event-driven projections with Entity Framework</a>). We can also trigger a workflow. For example, the orders module can start processing the order by subscribing to a shopping cart confirmation event. After we start the process, we can store a follow-up <em>OrderIntiated</em> event. The financial module can take it from there, and after getting the event, issue a proforma invoice. Then the payment and shipment processes can start accordingly.</p> <p>A single event can often cause a chain reaction and trigger changes in many places.</p> <p>How do we connect those workflows? It all depends on the guarantees we need in our system. Where I need the 100% delivery guarantee, I’m using an outbox pattern. If I can leave with the state being out of sync sometimes and <a href="/en/the_risk_of_ignoring_risks/">the risk is low</a>, then in-memory processing is acceptable. I often also join those two approaches.</p> <p>Subscriptions are implementations of the outbox pattern. They have a built-in retry mechanism when the event handler fails. Therefore, usually, when I get an event from them, I send it to the internal, in-memory bus. Even if an exception is thrown, it will be retried anyway, so I lose nothing. I have to <em>only</em> ensure that the service is idempotent.</p> <p>The MediatR library is the basis for such implementations in .NET. It allows you to separate the logic related to the handling of the command or event, thus creating loosely related components. It all happens in memory.</p> <p>MediatR broke into the world of .NET as the basis for the implementation of CQRS. I used it also. Some people love it; others hate it. It’s indeed a straightforward tool to use and reduces boilerplate. The downside is that most people do not use its capabilities like pipelines and behaviours. Without them, it may be overkill, which will just blur the application flow. It might be an unnecessary overhead if we don’t have more complex flows but just push events through it. It also forces <em>marker interfaces</em> for message classes (<em>IRequest</em> for the query or command, <em>INotification</em> for events) and their handlers. It sometimes obscures the picture and makes it challenging to analyze the flow. And some magic related to dependency injection and reflection.</p> <p>Some time ago, I was hooked by a colleague asking if MediatR could do more extensive event handling flows. It turned out that it couldn’t. It can do this for command and query, but it’s just broadcast for events.</p> <p>We often need to make a more complex flow. For instance, having the <em>UserAdded</em> event:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">UserAdded</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> FirstName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> LastName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">bool</span></span> IsAdmin <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We may want to create a pipeline that will at first filter admin users:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">IsAdmin</span><span class="token punctuation">(</span><span class="token class-name">UserAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> @<span class="token keyword">event</span><span class="token punctuation">.</span>IsAdmin<span class="token punctuation">;</span></code></pre></div> <p>Then map events to a dedicated <em>AdminAdded</em> event:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AdminAdded</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> FirstName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> LastName <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">AdminAdded</span> <span class="token function">ToAdminAdded</span><span class="token punctuation">(</span><span class="token class-name">UserAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>FirstName<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>LastName<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then handle mapped events storing information about new admins:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">AdminAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> GlobalAdmins<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And distribute global admins to all tenants:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">List<span class="token punctuation">&lt;</span>AdminGrantedInTenant<span class="token punctuation">></span></span> <span class="token function">SendToTenants</span><span class="token punctuation">(</span><span class="token class-name">UserAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> TenantNames <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>tenantName <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">AdminGrantedInTenant</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>FirstName<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>LastName<span class="token punctuation">,</span> tenantName<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AdminGrantedInTenant</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> FirstName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> LastName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> TenantName <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">AdminGrantedInTenant</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> AdminsInTenants<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>WithMediatR, doing something like this would be overwhelming. As an exercise, I decided to write my <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Sample/EventPipelines/EventPipelines/EventBus.cs">EventBus</a>, which would allow me to compose it as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">serviceCollection <span class="token punctuation">.</span><span class="token function">AddEventBus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Filter</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserAdded<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>IsAdmin<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Transform</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserAdded<span class="token punctuation">,</span> AdminAdded<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>ToAdminAdded<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Handle</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>AdminAdded<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>Handle<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Transform</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserAdded<span class="token punctuation">,</span> List<span class="token punctuation">&lt;</span>AdminGrantedInTenant<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>SendToTenants<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Handle</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>AdminGrantedInTenant<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>Handle<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>or without DI container:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> EventHandlersBuilder <span class="token punctuation">.</span><span class="token function">Setup</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Filter</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserAdded<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>IsAdmin<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Transform</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserAdded<span class="token punctuation">,</span> AdminAdded<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>ToAdminAdded<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Handle</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>AdminAdded<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>Handle<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Transform</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>UserAdded<span class="token punctuation">,</span> List<span class="token punctuation">&lt;</span>AdminGrantedInTenant<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>SendToTenants<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Handle</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>AdminGrantedInTenant<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>AdminPipeline<span class="token punctuation">.</span>Handle<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> eventBus <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventBus</span><span class="token punctuation">(</span>builder<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>I have roughly achieved MedtiatR with superpowers for events. Additionally, the composition is sprinkled with syntactic sugar without the magic of reflection.</p> <p>So far, it can:</p> <ul> <li>filter events,</li> <li>transform them,</li> <li>NOT requiring marker interfaces for events,</li> <li>NOT requiring marker interfaces for handlers,</li> <li>enables composition through regular functions,</li> <li>allows using interfaces and classes if you want to,</li> <li>can be used with Dependency Injection, but also without through builder,</li> <li>integrates with MediatR if you want to.</li> </ul> <p>See the full sample in my <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Sample/EventPipelines/EventPipelines/EventBus.cs">Event Sourcing in .NET</a> repository.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. if you liked this article, then check also similar:</p> <ul> <li><a href="/en/how_to_register_all_mediatr_handlers_by_convention/">How to register all CQRS handlers by convention</a></li> <li><a href="/en/cqrs_is_simpler_than_you_think_with_net6/">CQRS is simpler than you think with .NET 6 and C# 10</a></li> </ul> <p>p.s.2. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Are Temporal Tables an alternative to Event Sourcing?]]>https://event-driven.io/en/temporal_tables_and_event_sourcing/https://event-driven.io/en/temporal_tables_and_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6c01a039e3492455c5f497948c8e1a87/332ff/2022-04-20-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA7AAAAOwAFq1okJAAACjUlEQVQoz21TbU9SYRjmdzXXMgJ5Bw/iRIXFavrBpZYhkMABjhwEkWlHWdEkHQm+4AF5Oeo5vBjiC47pZn3IrdlW+aHlpjZxkR5OA/WE1fPpfvbc167rue7rZlBVp1QqURT1q/hzE4+iY7b4Cwh196dR39f9j5WXckd1P4OuSJKkKOrzh90AqJrsZoXNQMLeiMGyEAh4e4SRV46z01Oa4Aa4VEHubee8XdyVYeWOtz1lF4d0zBjIIQbqs2MPgwbJ8yeKwmmhGs+gLyfHJ+Pq5q2X7euIYsUmJCDeookX1bNiRnYc5GaQBwGNKOxx0hqvwCR5QVFUGvXFrY0bSGvSKsZN/GgfJ6RhRvvq4gZeRM+b7WVnkDbPI/7Bp/0KH3kJrph0Tr6xduVGlUtmPmbg4EY+bhEn+sUEJAlp69DeeyEdi3DIp3ol6xha5rso8zGuNB+deDXyLbcqZZMkTMJkP5AbaV4dlK0NNWFGbszACaprl6wNYZN8ZW6CFsu4dL9YPJ8CVauu5qRVQEAizMjPI61Zl/ytXYqbBVE9b07NJGxS1ChbmhihDWbQH/DD3WvDiqxTlnFIFvXcZYtoY7glaZWEtewFHcv/+HbC3jDzDFhdCFQxX1fL069RvXgduZ8dkhIW0YKGFXzKnFczpzprZnru+rtr0i7FpK7p28EXekB/RnX4/dDZJkg4W5L2hjTMTcEiDBSgWnZEz/F33iIcLTN9QGDETGv+OyTv8puDqjvLA/KcW4lDAhziZ5xSAhZjsNynBXwO7Vnhn5BcxbPi/vv8JqzijHfURkxAxAzELPXTWhHSwfMO9Pw4Pvp/PK8Xo8xfKJyl5n3RUXDWpQsiUNzn2dvdIclSeS43F+M3yJFn/CVrj+oAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/6c01a039e3492455c5f497948c8e1a87/a331c/2022-04-20-cover.png" srcset="/static/6c01a039e3492455c5f497948c8e1a87/36ca5/2022-04-20-cover.png 200w, /static/6c01a039e3492455c5f497948c8e1a87/a3397/2022-04-20-cover.png 400w, /static/6c01a039e3492455c5f497948c8e1a87/a331c/2022-04-20-cover.png 800w, /static/6c01a039e3492455c5f497948c8e1a87/332ff/2022-04-20-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I’ve been asked recently at least a few times about <em>Temporal Tables</em> and how they relate to Event Sourcing. Beware, and don’t confuse it with <em>temporary tables</em>. They are not temporary, just time-related. They are a solution that can help you solve a few cases that Event Sourcing solves, but they’re much different in the intended usage.</p> <p>Temporal tables are automatically generated tables with the history of the state of the selected table. For example, you can tell the database that for “BankAccount” table should automatically generate a historical table. After each record modification (Insert, Update, Delete), this table will get a new record. This record will contain the current state. So we’ll be getting a complete history of states for each record. Please do not mistake it with the delta, so information of what has changed. You won’t know what has changed. You’ll see just the series of states.</p> <p>That’s nice for auditing and doing time travelling (so seeing the state of the specific record at a particular time). Usually, we have to generate such tables ourselves in the application logic, which is expensive both in terms of development and performance. With Temporal Tables, we’re getting that out of the box. Tables and changes will be generated automatically.</p> <p>Temporal Tables may sound similar to <a href="https://en.wikipedia.org/wiki/Change_data_capture">Change Data Capture</a>? Indeed they are. We set them up for specific tables and get history. The difference is that we have much easier access to such tables through the application layer. We can, for example, create a query like this:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">SELECT</span> <span class="token operator">*</span> <span class="token keyword">FROM</span> BankAccount <span class="token keyword">AS</span> <span class="token keyword">OF</span> SYSTEM <span class="token keyword">TIME</span> <span class="token string">'2020-05-17 15:45:00'</span> <span class="token keyword">WHERE</span> id <span class="token operator">=</span> <span class="token number">3</span></code></pre></div> <p>Such a query will return the state of our record to that specific point in time. Therefore, we can go back in time and view the state of our records. So it’s a bit of Event Sourcing.</p> <p>But why just a bit? As you know, a single business operation can update multiple tables in the normalised relational database. We only get a history of the record state. That may be fine for basic audit purposes, but out of the box, it doesn’t have business information and correlation to changes between tables. We can investigate changes for specific records with temporal tables, but we don’t know the correlation and what caused them. To have a full audit, we should know what happened (i.e. the event) and what action (i.e. command, request) triggered the change. Only then we can assess whether someone who performed this operation was entitled to it. Temporal tables are just technical logs with a list of historical records. It’s not keeping the business information. To get it, you’d need to correlate data from multiple temporal tables, make diffs and still guess what precisely has happened. Event stores usually can guarantee ordering in the context of the entire system, allowing to know the whole story.</p> <p>What’s more history of changes and audit log is just one piece of event sourcing. It’s just an entry point. Event Sourcing enables Event-Driven Architecture. After having business events, we can use them to “glue” workflow steps and trigger other processes. We won’t have it in temporal tables.</p> <p>Nevertheless, temporal tables can be a pragmatic solution that will allow you to fulfil some scenarios, and it is undoubtedly less revolutionary than the introduction of Event Sourcing. It has already entered the <em>ISO SQL:2011</em> standard; however, few databases have implemented it yet. MSSQL from version 2016, MariaDB, and Postgres has a plugin (but the last version was released in 2017).</p> <p>More to read:</p> <ul> <li>MSSQL 2016 - <a href="https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables?view=sql-server-ver15">https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables?view=sql-server-ver15</a></li> <li>MariaDB - <a href="https://mariadb.com/kb/en/temporal-data-tables/">https://mariadb.com/kb/en/temporal-data-tables/</a></li> <li><a href="https://wiki.postgresql.org/images/6/64/Fosdem20150130PostgresqlTemporal.pdf">https://wiki.postgresql.org/images/6/64/Fosdem20150130PostgresqlTemporal.pdf</a> - Slides describing the general idea and, more specifically, how to introduce it in Postgres.</li> <li><a href="https://pgxn.org/dist/temporal_tables/">https://pgxn.org/dist/temporal_tables/</a> - the mentioned Postgres plugin.</li> <li><a href="https://www.youtube.com/watch?v=Pn4HjRlvQF">https://www.youtube.com/watch?v=Pn4HjRlvQF</a> - Dino Esposito’s presentation on this topic.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Agile vs Introverts]]>https://event-driven.io/en/agile_vs_introverts/https://event-driven.io/en/agile_vs_introverts/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0ae9fa4c36213854bc7aa47833a7726c/332ff/2022-04-13-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA7BAAAOwQG4kWvtAAADYUlEQVQozwFWA6n8ANPIvqCOfeXi4ODFu+7d1v/////+/f////3+///+/efn68HM6PD0/f///v////Dj7qFffODT1eqyy/vd8ADZvKHdsono3tju4tvw3tT////89fnv6/Dv7e2roaJGN0qZmrfFz/Ht7/j6+/3//f+QcIWJb3OaeIbq3ugA976e141q2aqSu6Sa7s7D6Liy8b6747i10KOblHh+m3JxzKCSW1l4NUqGYHKizM7X5cO3q31x4MC50ammAOVtYcddPIo7KIBYSua3pMtuXLpKP+O7t5VjU3E4MohgZYtmaYx7g9zJwc6/uXhhZuC4rNajnNaDdd6hkgD0mKX1nJltR0NFAADGbmHAeG1LAACecGLIsJsqGhGYh4DnsKXUc2PiqJfvu5+7iHJ6TE2rdWe/lIqphH4A6iIt+5qd////vnl9cBAObSYiFw0KzZR40o9wAQMBZyochy0d0pSKtW1hu3lmYEA2CwULnVpFwH1yv3NmANVscd2PhcuXf//895uOkQsAADMUEZ9YSalUQJGLiVBQVEQ0LuyXfXU0K79mT8GVhm5lXyIKBqFZTsBgTwDXppP/6dzEeV7q49/68OktHhczCAmNRzGMXVH////y3tfyrI3ukG+keWnOakXZl33/17hTQDUhCwlaFg4AMRcWtH9s5oxw98u168SiNRQMOiMPgk8cp4Jw/+jd3Zd+8qOB4oxy6ntcnFI5lmBL3KaPpGpRFw8Mh3FyAAAAAB8dGlM2LpdIM7VfQRUAAHVCIPizi/jRvVMaC1U1LOmEX6s8G64vENeKeW9BNE8aD2slEaGPiv///wCRSCofEQsAAAAqFA5cJRYHAAB1Ri/6tJPchGJ2TTiihHI0HxcDAABDGQ+9WUa1l46AZVvn0cj08OzfrJ0Awl4ysmI/l08tSxwKEwQAAAAAYjMg6INZ5oVdsV88JhkSAAAAHh4gBgkKAAAAkFVH/9zL5rWmvlk9qj0fADoTBXM3Hu2JVNeuizg7M5KPjHtTQbJrT/LWyJ9rTAsAADwxP+LXzxgWFAAAAB4QDdOYfIcyI6RGOcqCawAQDgkAAACHQiD/4LP36b749uTy2sju2svr1szex7QpDQCtucHcvJ4EAAAAAAAjEA2/m46IUEq3b2jKmpIksrMvVjH3NQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/0ae9fa4c36213854bc7aa47833a7726c/a331c/2022-04-13-cover.png" srcset="/static/0ae9fa4c36213854bc7aa47833a7726c/36ca5/2022-04-13-cover.png 200w, /static/0ae9fa4c36213854bc7aa47833a7726c/a3397/2022-04-13-cover.png 400w, /static/0ae9fa4c36213854bc7aa47833a7726c/a331c/2022-04-13-cover.png 800w, /static/0ae9fa4c36213854bc7aa47833a7726c/332ff/2022-04-13-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I had this thought: most of the “Agile” methodologies and tools are invented by extroverts. Perhaps this is also why, although they are popular, they are still not a standard for designing software. Why do I think so?</p> <p>Let’s start with a confession; I am an introvert. It might not be visible at first glance. I am often outspoken, and I am not afraid to state my opinion. As you might know, I’m a public speaker at conferences, and I write on this blog. However, that doesn’t make me an extrovert. Introverts are not necessarily timid people, robbed of interpersonal abilities or avoiding others. Of course, for some people, this may be the case. However, it usually means that our energy reserves for interacting with others run out very quickly. We drain much faster than regular people. After such a period, we need to be alone. Lock ourselves in our cave and recharge batteries. Without such a break, we get burned out and irritated. If you are also an introvert, you must know all too well what I am talking about.</p> <p>It seems to me that there are more introverts in our industry than statistically in other places. It is, of course, unsupported by evidence, but the stereotype about IT people is not accidental. Despite this, the agile approach to management has received applause broadly in recent years. It has even become fashionable, especially among “influencers”.</p> <p>I am a big fan of the agile approach and close cooperation between the business and programmers. I wrote about that on my blog numerous times, for example:</p> <ul> <li><a href="/en/bring_me_problems_not_solutions/">Bring me problems, not solutions!</a>,</li> <li><a href="/en/sociological_aspects_of_microservices/">Sociological aspects of Microservices</a>,</li> <li><a href="/en/how_using_events_help_in_teams_autonomy/">How using events helps in a teams’ autonomy</a>.</li> </ul> <p>However, I also see problems with the agile approach. I wrote about them:</p> <ul> <li><a href="/en/when_agile_is_not_enough/">When Agile is not enough</a>,</li> <li><a href="/en/architect_manifesto/">Architect Manifesto</a>,</li> <li><a href="/en/the_risk_of_ignoring_risks/">The risk of ignoring risks</a>.</li> </ul> <p>In my “bubble”, I see statements that if you don’t do Event Storming (or other similar practices) or <strong>you don’t do pair programming, mob programming, then you don’t do “real agile”</strong>. These techniques are excellent tools; I’ve used them in my past projects. Yet, as with everything, they’re not silver bullets. They work well as long as they are used wisely and without dogmatism.</p> <p>As an introvert, I do not feel well when I have to spend a few days as an active participant in the workshops and I am forced to do pair or group programming. I see the significant advantages of these solutions: they emphasize cooperation and relationships between people. Still, I do not like when they are presented as “the only right way”. Agile comes from being flexible and adapting the process to the people. Extroverts like action; they love it when something happens, and a work emerges from chaotic explorations and working together. Introverts? Not necessarily. Of course, they can take and be an active participant in such events, but after a day of this type, they feel washed out. <strong>I am physically and mentally exhausted after a day at work meetings or workshops or after a lecture at an evening webinar. I need to regenerate. I have the impression that most of these methodologies do not consider this.</strong></p> <p>They lack breath, write down conclusions, think ahead and upfront thinking. We often hear an anecdote/urban legend from the facilitator that “there was a guy at my training, who did not speak during the entire training. And after a few hours of workshop, when we couldn’t solve the serious problem, he spoke up and provide the insight that solved the case.”_ It is a lovely fairy tale. It could have happened sometimes, but… It would be better if these methodologies also considered introverts’ needs, for instance, giving them a moment to rest or rethink the approach in a calm environment.</p> <p>Perhaps all of that is one of the reasons for unsuccessful workshops. Sometimes they’re like a comet. They happen, come and go and leave no significant changes, as it evokes a defensive attitude in the silent majority. It seems to me that if these methodologies were mixed with async practices such as:</p> <ul> <li><a href="/en/fifteen_tips_on_how_to_run_meetings_effectively/">preparing for a meeting by writing down what is to be done</a>,</li> <li><a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">writing the proposal in the form of Architecture Decision Record or Change Request</a>,</li> <li>more upfront design</li> </ul> <p>or a sandwich of workshop and time to reconsider it peacefully, it would not only be more effective.</p> <p><strong>It could be even more… Agile.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[The risk of ignoring risks]]>https://event-driven.io/en/the_risk_of_ignoring_risks/https://event-driven.io/en/the_risk_of_ignoring_risks/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b39decc1fa7b955ea1b4d8533efa1797/332ff/2022-04-06-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA7CAAAOwgEVKEqAAAADYUlEQVQozwFWA6n8ABMtFRUoFB8mFyscFw8PEB8XFTIhHz83MVI9MjkgMiQhTTwsM4ggIqUTFWEwKsnCrM/EoSsmFi4zKF5fUQCjvp62vLDRyb2ZhHZ2aGC5srDUyMTr5t/Jta1zICxXJyxBNzQyJyM8KiA5LCR0Y1pPTTotQElgY3Wsm6cA+/P++On/6tnZzL6w+e/x/vP58sLGxXiAfExzPSd2R0GYT0WJPDFXVEhaXk1NPy0pIxgfJiBcPRpmbBxiAMDlxp/OomyhblqVX3qtjp+5vqJeT9k5Or5NWINlkp+WtauXtYBxwT4/okxIUi82IEdQQEpOR2Y8RoVLVwBovnhcuW5jwXVeu22EwZG4z7+MpIyyoKHCq7SYjI7DsbbDtL1Tg4VBiW1Cj1BKnlNltG+b1KWPzZVhtWIAZrl5crB/gKSGmaGXs6SrvKWxsJqnmIqUpJacbmNpmo2U0bvHjb6MZsBwb8B9b799Zrp1aLt2otmwtuXHAJSVk6OPnLSaq4p2gDYvM21iaKCRmYV4f4t+hHhtc6KUms22wqa7qpbOpZXMopTOopvRqZrSqXG+gXLAgQCokaCnlKCunKVbVFQAAABlXGPArbbCrbXHsrzLt8DNucLPucawubCQx56UyKGLxZm03MKl1LRqt3pPq1wAppOgqpWjr5uoqpehkoGKrZqkt6Kst663u6y2uqiytq61v663sLKsRplTRppTRptTS59YSqBYTqJcUqVeAJ+NmqCPm6CPmqSSm6ObpJaUnKKboqt1eqaPlKqNkLJ5e6qmqLGsq1SYYEWVU0iXVUmZV0ucWEudWEqeVwCdjZmfj5qgkJiflp6tZGnDISaxbHO8TVG5Y2e7Yma7aWytqq2yqqtXkF86iEc/i0s/jExAjU1Bjk5ElFEAoJKcoZKcopOcopqislpbvSstqoCEr3BzppSYrn+Bppudo6Cgp6CjV4hdNoI/PIREO4VFPYhIPopKPopKAJySnJ2Sm5ySmpyTmZqaoJmcoJmZnZmYmpiVlpabm5WVlI6QjoKHg0h7TTR8PDl+QDiAQDuDRDyFRTmCQgCLiY+Jh4yGhYeDhIV/gYB7fnx0e3Zud25kcmhXZ11JW1E7VEMvWDYqYi4vcjUzdTkzdzk0ejwzeDo0fD1GPqUpO4aEgwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b39decc1fa7b955ea1b4d8533efa1797/a331c/2022-04-06-cover.png" srcset="/static/b39decc1fa7b955ea1b4d8533efa1797/36ca5/2022-04-06-cover.png 200w, /static/b39decc1fa7b955ea1b4d8533efa1797/a3397/2022-04-06-cover.png 400w, /static/b39decc1fa7b955ea1b4d8533efa1797/a331c/2022-04-06-cover.png 800w, /static/b39decc1fa7b955ea1b4d8533efa1797/332ff/2022-04-06-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Do you like playing poker? I used to play Texas Holdem with my friends regularly. We took it easy; each person put 5 PLN into the pool, and the winner took everything. Yet, this staggering amount was enough to keep the players motivated. Of course, poker is a gamble. There is some randomness, but it is pure statistic applied in practice. If you want to play it well, it’s not enough to bluff well or play with a good hand. You have to take risks. We have to analyze the probability that a given cards combination will appear on the table for every hand. When it turns out that the risk of loss is small enough, we enter. If it’s too high, we fold. We can also check and see what will happen next.</p> <p><strong>When working on design, I noticed that we often forget about risks and alternative solutions.</strong> We are afraid to admit to others and ourselves that our solution is not perfect and that it could be made better. Our vision is so tunnelled that when we see the light, we’re running towards it, forgetting that it may be a train coming from the other side.</p> <p>Nothing irritates me more when someone comes to me with a solution and when I ask: <em>“What other alternatives did you consider?”</em> I hear <em>“we did not evaluate others as this one is good enough.”</em>. I react similarly when I hear <em>“we don’t have time for that”</em> to the suggestion of verifying another option. Why? Because it’s usually just an excuse. It usually means that the <em>“solution”</em> is not backed by analysis or experience. Usually, this is because someone is so focused on their way that they don’t even want to check other possibilities. My way, or highway.</p> <p><strong>Ignoring others’ opinions, alternatives, and risks can end up badly.</strong> The later the overlooked risk is fulfilled, the greater the cost is. If we use ADR (Architecture Decision Record - see my article: <a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">How to successfully do documentation without a maintenance burden?</a>), we should always describe the consequences of our decision. Even in the best decisions, the outcomes are seldom only positive. Moreover, we should accept that sometimes our proposals are rejected. It doesn’t have to mean that they are wrong. They can be technically sound designs, but for various reasons (e.g. time, finances, team experience), they are not applicable at a given stage of the project. We gain essential knowledge if we precisely describe why we rejected a given decision. First of all, we have a clear message why we gave up on something. Secondly, we already have a subject that has been researched. It can help us return to it in a while and verify if our situation has changed. Maybe we should re-review it and accept it this time.</p> <p>Similarly, we should describe precisely what stage of the project we are at and what decisions we make. <strong>We should define the validity period for our decision.</strong> For instance, we describe our assumptions as valid for a specific load, assuming that it’s private and not public API. Why? We should not decide based on imaginary scenarios. We should base them on precise assumptions, considering we will have 100 requests per second. We can decide to design a solution that handles 1000 per second. If that comes without much additional effort, it’s worth doing that to have a safety buffer. We could increase this solution to 10,000, but this would mean much more work. Having that, we chose a solution guaranteeing processing 1000 per second. And it’s ok that we take the risk that if more than 1000 requests come, our server may fall because it is 10 times more than the assumed traffic. If it turns out that we got 10,000, then either:</p> <ul> <li>we made a mistake in the analysis,</li> <li>we ignored the increasing traffic and the validity of our solution,</li> <li>we have achieved spectacular success, and we have many more users than we could have imagined. This is a problem worth having.</li> </ul> <p>Of course, we should think not only about risks related to technical design. Changes to the business domain or wrong problem definition can be a showstopper more often than a technical mishap. Read more in <a href="/en/bring_me_problems_not_solutions/">Bring me problems, not solutions!</a> and <a href="/en/when_agile_is_not_enough/">When Agile is not enough</a>.</p> <p><strong>The <a href="https://monday.com/blog/project-management/risk-register/">“Risk Register”</a> can help us in such analysis.</strong> Yes, it sounds pretty formalistic, it is popular in “stricter management methodologies”, but who said that only agile methodologies can have reasonable ideas?</p> <p>The risk register is a simple table. We write down all risks for our solution. 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, then it means that we have 2 and 7 in our hands, and it is better to fold. If the risk is unlikely with little consequence, we have two aces, and we can go all-in. Usually, however, it is somewhere in the middle. We should write down what we will do when the risk occurs (e.g. what we will do when we have 10,000 requests per second).</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ba32d7a50a6aa4b40d00e3964feb26bd/58e7d/2022-04-06-risk-matrix.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 61%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAMCAIAAADtbgqsAAAACXBIWXMAAAsTAAALEwEAmpwYAAACvElEQVQozx3B229LcRwA8G+7tZt1bOOl7azriFlExGUYZsuwm2yjNlmCxCN/gHgicYmIy4sHT3jw5IV/gAdCIsz2MJV21oujPe0553duv9s5PW2PHuHzgckL+uxla/Ki1T8s9x4s9Q8bA6N42xG0Z9zeNcb3T0hTF9TpS3b/sLpj2Ng7Wdk5YvYNybGD6slFHSSZEFrl3F1dzb599zn5Q8gL6sdPK78ElMlJHz8tryz/KJX0L1+TS0upQsFY+pZ6/+FbOl2UJAKOU/H+03VULOYNXcGm9lvIiKJgmmpByBULgmloGpIEIYuUMme4YtE/br3qOEApc/+plyVULGvccSWERRFJkioI5SKSi6gsqnJOLKzncrKmk0ql3vBqboPYNnz/vloo/HIcz2ZP6nygRoeq/IBjH6L8sKUO4vFD1uApPjhS2rlbP3aUnRhD+/ax48f56Kh6+jSsra3JSqla8X6z68sUlnBzigHnoGEgCPQJ4ENBetSvdgELtJjBoAJAg0Ha1IS6uyGVSpVk0dHqT43bx+SOkXL8Cg57lo9oTVXut+pNiPmo59ev+UlwC45sUfw+2tFBN29GfX2QTCbTP1M1s/HcuD3FQwnac5GGH5LOe2rnY7NTw/6aAjUXjKtgQsgItEkAuLWVtLYqsRhkM5mSLLrYe2bcGrfaz9GeWRadwN1jKDpjRlfslrzRnPUCxZs+a3sHHdykdAFpayMbN6C+GKTTqWw+4xLvBbk7ztsTrOcsi85XoudYdN6KnMeRRSU8a0Tf6KFaHjTiM+4AhhDxtaNwDDKZjEmNBvVe4DuneFuCbZ2jkTN2ZI5G5nh4hoZn9PAJHHnFQg0Opgv4PfAbAfag2XgUg/X19ZIsehXvJb0/RboWzG3zNL5gxRN67wKPz5u9CbFnWo2/pl1VLaDILZwHqRtENrD69r9mdcnQytHCQAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ba32d7a50a6aa4b40d00e3964feb26bd/a331c/2022-04-06-risk-matrix.png" srcset="/static/ba32d7a50a6aa4b40d00e3964feb26bd/36ca5/2022-04-06-risk-matrix.png 200w, /static/ba32d7a50a6aa4b40d00e3964feb26bd/a3397/2022-04-06-risk-matrix.png 400w, /static/ba32d7a50a6aa4b40d00e3964feb26bd/a331c/2022-04-06-risk-matrix.png 800w, /static/ba32d7a50a6aa4b40d00e3964feb26bd/58e7d/2022-04-06-risk-matrix.png 1188w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Our math doesn’t have to be super-precise, but rather a general plan that will allow us to act. Based on such an analysis, we can take actions that will reduce the likelihood or consequences of the risk occurring. The Risk Register should be continuously updated and evaluated. We should not expect it to be flawless. Once we see that we’re close to the pessimistic scenario, we should take corrective actions or explore new ones.</p> <p>Taking risks is ok as long as we are prepared for it. It’s much easier if we have calculated the probability and the consequences that may await us. We should not be afraid of it or ashamed that our solution is not bulletproof. Nobody is. Even Achilles had a delicate heel. <strong>If we accept that we may fail, our solutions will be much more resistant to error, and we will be better prepared for it.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Twelve things I learned about Java during my last code review]]>https://event-driven.io/en/12_things_I_learned_on_last_pull_request_review/https://event-driven.io/en/12_things_I_learned_on_last_pull_request_review/<p>As I have mentioned to you many times, I like to test different approaches, technologies and solutions. I may not have told you yet that I once wanted to be a Java developer. I became a .NET developer only because I couldn’t get any job as Java Developer. After long looking for a job, I was finally hired as a C# developer, and it stayed like that for a long time. I don’t regret it, as .NET moved forward, C# language evolved a lot, and Java stagnated at some point. I’m talking about the language because the community, especially the OSS, has always been strong and vibrant. I have always envied the maturity of Java tools, especially those for “enterprise” and production-ready solutions. From time to time, I participated in Java projects (probably the longest in Android), now I also contributed to the <a href="https://github.com/EventStore/EventStoreDB-Client-Java">Java EventStoreDB client</a>. I recently checked how my Java skills held up and did an Event Sourcing example.</p> <p>I decided to port <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/EventStoreDB/Simple">my C# sample</a>. My main assumptions were:</p> <ul> <li>explain the basics of Event Sourcing, both from the write model (<a href="https://developers.eventstore.com/">EventStoreDB</a>) and read model part (<a href="https://www.postgresql.org/">PostgreSQL</a> and <a href="https://spring.io/projects/spring-data-jpa">Spring Data JPA</a>),</li> <li>present that you can join the classical approach with Event Sourcing without making a massive revolution,</li> <li><a href="/en/cqrs_facts_and_myths_explained/">CQRS</a> architecture is sliced by business features, keeping code that changes together simultaneously. Read more in <a href="/en/how_to_slice_the_codebase_effectively/">How to slice the codebase effectively?</a>.</li> <li>Composable functions for command handlers, events, projections, query handling, minimising the need for marker interfaces. Thanks to that testability and easier maintenance.</li> </ul> <p>The presented use case is Shopping Cart flow and is modelled as Web API written in <a href="https://spring.io/projects/spring-boot">Spring Boot</a> and <a href="https://www.oracle.com/java/technologies/downloads/">Java 17</a>.</p> <p>After I finished the initial version (<a href="https://github.com/oskardudycz/EventSourcing.JVM/pull/1">https://github.com/oskardudycz/EventSourcing.JVM/pull/1</a>), I decided to ask the “community” for a review. I wanted to make sure that what I did, was not just a dumb port but the code that’s idiomatic and similar to what typically is done in the Java World. That’s especially important, as I was doing so far mostly infrastructure code in Java, and business-related may differ from it. I wanted to get sharp but honest feedback.</p> <p>Well, I got what I asked for!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/00dd435a69963311e2d7f66d6a42d6f1/332ff/2022-03-30-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADHklEQVQozyXRW2/adhiA8deObWzjw98QIBhjDnGAQjgEgjkfcqgLhJBAIBBKCKmSMBGNtbTqlLXd1sNWpdM07aoXVW+6LzBpl7vc5b7UtPYbPD898PJE//DLo9tXN7P+3T8//f7x3ZO389bb76Y/PL78ZlSbDWuLi8Grp9fjg7vjg8bpfu3qfn/SbZbDiocGmGYUI7/eq+eGRe/Pi8HXo3tfDYzFRe/5fLyY7M9HeyNjs7O1aaQ0I+Yva65qyK+7HakV0x0BILIEzbh11tLiPvG4XX/x8PzHxdlFS382n3y/uFqct/sRQWVgFVFxCY+xECDAj8EdM1T9LKTt5O0wElGtOGAunjwqBQ8L4UFJG1dXB3n1UFe/zVgWcfM6ItMuNmUlAwzEODxnIbIKD1fbcrfg6cQsNyXfH8PMuxevSVRCHBuQpW7WW096sgr/Wwr7tREqRrWKyu7KbGV5yc+AAACTitLKqoOK77rkO9e9/f4UAOEYAICZJjVlBXDThzc3//79F8mY9xLulofVOXAToBAAO+u2RtLZyrg7Ba/XHdSzu2aWAwAMwwjSZLM7TSTx+qfbWqMJAJIoZpaJqgiaCQI0BrtRe33D2UwpnYy8HQ9cTp+55BUAIJYICVmQKEhIslutDEVJAi8gKYaWdpYxhcWDHA73oo56Qt7fVA7S6lHaqcdyZlEBAJ4XEZI4luVYRuA4h81us1oomkEMlZaINZ4MiSR0U7a8JhphtL8h93Ke7aif4QMYTlqkZZZlGZqmKZITRZYXKZKSJY6lGYuJ1ARqTTBBbY3Mu2DTCQUVr/opIyhIKBQKRhFCX+QAELAhXbFQBCHShIkkeAJcBDhIDCox39hINXLhZjF6vJs4LMfSSf3Tw6GRSfi1NY+qqh715bD1z6PWm9mZw+VWZWenlDw18ht+BUZ75evT/uKsOz/vzSbDq9OT98+fPB0e5taDrYN2uViIR0KZfKG2VTo+apcr1UxqY9Jvd+o7yVUVRAoqDqztJaZFuRcTUjbqMh128qyJIkUzz9I0Q/yf/nk80AReDClH5cR4b+vxg5P/AILHmZQsz1OhAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/00dd435a69963311e2d7f66d6a42d6f1/a331c/2022-03-30-cover.png" srcset="/static/00dd435a69963311e2d7f66d6a42d6f1/36ca5/2022-03-30-cover.png 200w, /static/00dd435a69963311e2d7f66d6a42d6f1/a3397/2022-03-30-cover.png 400w, /static/00dd435a69963311e2d7f66d6a42d6f1/a331c/2022-03-30-cover.png 800w, /static/00dd435a69963311e2d7f66d6a42d6f1/332ff/2022-03-30-cover.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Okay, it wasn’t that bad, but it cost me “a bit” of work. And that’s awesome, as my code and my skill improved by that!</p> <p><strong>Let me share what I learned:</strong></p> <ol> <li> <p>In Java, the type Optional should only be used as the result of a method. It was created to reduce the <em>void</em> type confusion. It should not be used as a parameter or input field for a method. It makes sense because generics in Java are basically templates that exist before compilation (<a href="https://www.baeldung.com/java-generics">https://www.baeldung.com/java-generics</a>). After that they’re just wiped out. See more about the initial <em>Optional</em> type design at: <a href="https://nipafx.dev/design-java-optional/">https://nipafx.dev/design-java-optional/</a>.</p> </li> <li> <p>I’ve already used <a href="https://openjdk.java.net/jeps/360">sealed interfaces</a> to get a sweet pattern matching when rebuilding the state from events. However, it turned out that they allow the full use of union types! Thanks to the suggestions, I went further and created this shopping cart definition:</p> </li> </ol> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token keyword">sealed</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token class-name">UUID</span> <span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">UUID</span> <span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name">ProductItems</span> <span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">record</span> <span class="token class-name">PendingShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> id<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> productItems <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">ConfirmedShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> id<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> productItems<span class="token punctuation">,</span> <span class="token class-name">LocalDateTime</span> confirmedAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">record</span> <span class="token class-name">CanceledShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">UUID</span> id<span class="token punctuation">,</span> <span class="token class-name">UUID</span> clientId<span class="token punctuation">,</span> <span class="token class-name">ProductItems</span> productItems<span class="token punctuation">,</span> <span class="token class-name">LocalDateTime</span> canceledAt <span class="token punctuation">)</span> <span class="token keyword">implements</span> <span class="token class-name">ShoppingCart</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">enum</span> <span class="token class-name">Status</span> <span class="token punctuation">{</span> <span class="token class-name">Pending</span><span class="token punctuation">,</span> <span class="token class-name">Confirmed</span><span class="token punctuation">,</span> <span class="token class-name">Cancelled</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span> <span class="token keyword">boolean</span> <span class="token function">isClosed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">this</span> <span class="token keyword">instanceof</span> <span class="token class-name">ConfirmedShoppingCart</span> <span class="token operator">||</span> <span class="token keyword">this</span> <span class="token keyword">instanceof</span> <span class="token class-name">CanceledShoppingCart</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span> <span class="token class-name">ShoppingCart<span class="token punctuation">.</span>Status</span> <span class="token function">status</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">PendingShoppingCart</span> pendingShoppingCart<span class="token operator">:</span> <span class="token keyword">yield</span> <span class="token class-name">Status<span class="token punctuation">.</span>Pending</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ConfirmedShoppingCart</span> confirmedShoppingCart<span class="token operator">:</span> <span class="token keyword">yield</span> <span class="token class-name">Status<span class="token punctuation">.</span>Confirmed</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">CanceledShoppingCart</span> canceledShoppingCart<span class="token operator">:</span> <span class="token keyword">yield</span> <span class="token class-name">Status<span class="token punctuation">.</span>Cancelled</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">static</span> <span class="token class-name">ShoppingCart</span> <span class="token function">when</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCart</span> current<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartEvent</span> event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartOpened</span> shoppingCartOpened<span class="token operator">:</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">PendingShoppingCart</span><span class="token punctuation">(</span> shoppingCartOpened<span class="token punctuation">.</span><span class="token function">shoppingCartId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shoppingCartOpened<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token class-name">ProductItems</span><span class="token punctuation">.</span><span class="token function">empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ProductItemAddedToShoppingCart</span> productItemAddedToShoppingCart<span class="token operator">:</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">PendingShoppingCart</span><span class="token punctuation">(</span> current<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> current<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> current<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">add</span><span class="token punctuation">(</span>productItemAddedToShoppingCart<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span> productItemRemovedFromShoppingCart<span class="token operator">:</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">PendingShoppingCart</span><span class="token punctuation">(</span> current<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> current<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> current<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">remove</span><span class="token punctuation">(</span>productItemRemovedFromShoppingCart<span class="token punctuation">.</span><span class="token function">productItem</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartConfirmed</span> shoppingCartConfirmed<span class="token operator">:</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">ConfirmedShoppingCart</span><span class="token punctuation">(</span> current<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> current<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> current<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shoppingCartConfirmed<span class="token punctuation">.</span><span class="token function">confirmedAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">ShoppingCartCanceled</span> shoppingCartCanceled<span class="token operator">:</span> <span class="token keyword">yield</span> <span class="token keyword">new</span> <span class="token class-name">CanceledShoppingCart</span><span class="token punctuation">(</span> current<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> current<span class="token punctuation">.</span><span class="token function">clientId</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> current<span class="token punctuation">.</span><span class="token function">productItems</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shoppingCartCanceled<span class="token punctuation">.</span><span class="token function">canceledAt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token keyword">null</span><span class="token operator">:</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token string">"Event cannot be null!"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>See full sample in <a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/samples/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/ShoppingCart.java">ShoppingCart.java</a>. I miss such a possibility in C# because I believe that it enables concise and precise modelling of data structures.</p> <ol start="3"> <li> <p>I’m a big fan of slicing architecture into vertical pieces (read more <a href="/en/how_to_slice_the_codebase_effectively/">in my other article</a>). I also believe that composition = simplicity. <a href="https://www.infoq.com/presentations/Simple-Made-Easy">Nevertheless, “simplicity” means something different to each of us.</a> Sometimes it is better to group smaller things to provide a more readable structure, such as the application service. Instead of going into many tiny slices, I decided to add <a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/samples/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/ShoppingCartService.java">ShoppingCartService</a> that shows what’s possible to do with Shopping Cart. It made things clearer, especially for newcomers trying to understand my sample.</p> </li> <li> <p>Spring Boot has a built-in <a href="https://reflectoring.io/spring-boot-application-events-explained/">ApplicationEventPublisher</a>, so you don’t need to build your event bus in memory. I had to go full circle and remove my “great” but totally redundant implementation. Remember: learn the tools before moving on to custom, custom implementations.</p> </li> <li> <p>Java requires defining exceptions explicitly that our method can throw. However, there is no point in pushing up all possible exceptions. It is like being holier than the Pope. If you don’t want to handle them in your code, it’s better to make them runtime exceptions and catch them in a global mechanism. Read more about the breakdown of exceptions to checked/unchecked: <a href="https://baeldung-cn.com/java-checked-unchecked-exceptions">https://baeldung-cn.com/java-checked-unchecked-exceptions</a>.</p> </li> <li> <p>The <em>ControllerAdvice</em> and <em>ExceptionHandler</em> annotations can be used to handle global exception handling. They enable automatic mapping of exceptions to HTTP statuses; see how I did that in <a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/samples/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/http/GlobalExceptionHandler.java">GlobalExceptionHandler.java</a>.</p> </li> <li> <p>Since I’m not a big fan of annotations/attributes/decorators, I manually register dependencies as Java Beans. I stand by my decision, but I think I have gone too far. It is better to compose stateless code than to delegate it to DI.</p> </li> <li> <p>The reviewers also motivated me to finish what I started instead of postponing it to additional Pull Requests. For example, thanks to their comments, I came up with a nice test syntax for Given / When / Then API:</p> </li> </ol> <div class="gatsby-highlight" data-language="java"><pre class="language-java"><code class="language-java"><span class="token annotation punctuation">@SpringBootTest</span><span class="token punctuation">(</span>classes <span class="token operator">=</span> <span class="token class-name">ECommerceApplication</span><span class="token punctuation">.</span><span class="token keyword">class</span><span class="token punctuation">,</span> webEnvironment <span class="token operator">=</span> <span class="token class-name">SpringBootTest<span class="token punctuation">.</span>WebEnvironment</span><span class="token punctuation">.</span>RANDOM_PORT<span class="token punctuation">)</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AddProductItemToShoppingCartTests</span> <span class="token keyword">extends</span> <span class="token class-name">ApiSpecification</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">final</span> <span class="token class-name">UUID</span> clientId <span class="token operator">=</span> UUID<span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">UUID</span> shoppingCartId<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token class-name">ETag</span> eTag<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token class-name">AddProductItemToShoppingCartTests</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span><span class="token string">"api/shopping-carts"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@BeforeEach</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">openShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> result <span class="token operator">=</span> <span class="token class-name">ShoppingCartRestBuilder</span><span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span>restTemplate<span class="token punctuation">,</span> port<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span>cart <span class="token operator">-></span> cart<span class="token punctuation">.</span><span class="token function">withClientId</span><span class="token punctuation">(</span>clientId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> shoppingCartId <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> eTag <span class="token operator">=</span> result<span class="token punctuation">.</span><span class="token function">eTag</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addProductItem_succeeds_forValidDataAndExistingShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">AddProduct</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ProductItemRequest</span><span class="token punctuation">(</span> UUID<span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"%s/products"</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">,</span> eTag<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>OK<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token annotation punctuation">@Test</span> <span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">addProductItem_succeeds_forValidDataAndNonEmptyExistingShoppingCart</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> result <span class="token operator">=</span> <span class="token class-name">ShoppingCartRestBuilder</span><span class="token punctuation">.</span><span class="token function">of</span><span class="token punctuation">(</span>restTemplate<span class="token punctuation">,</span> port<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">build</span><span class="token punctuation">(</span>cart <span class="token operator">-></span> cart <span class="token punctuation">.</span><span class="token function">withClientId</span><span class="token punctuation">(</span>clientId<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">withProduct</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ProductItemRequest</span><span class="token punctuation">(</span>UUID<span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">given</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-></span> <span class="token keyword">new</span> <span class="token class-name">AddProduct</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">ProductItemRequest</span><span class="token punctuation">(</span> UUID<span class="token punctuation">.</span><span class="token function">randomUUID</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">2</span> <span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">when</span><span class="token punctuation">(</span><span class="token function">POST</span><span class="token punctuation">(</span><span class="token string">"%s/products"</span><span class="token punctuation">.</span><span class="token function">formatted</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span><span class="token function">id</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> result<span class="token punctuation">.</span><span class="token function">eTag</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span>OK<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// (...)</span> <span class="token punctuation">}</span></code></pre></div> <p>Check more in:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/samples/event-sourcing-esdb-simple/src/test/java/io/eventdriven/ecommerce/testing/ApiSpecification.java">ApiSpecification.java</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/samples/event-sourcing-esdb-simple/src/test/java/io/eventdriven/ecommerce/api/controller/AddProductItemToShoppingCartTests.java">AddProductItemToShoppingCartTests.java</a>.</li> </ul> <p>I even got thrilled about how cool is that! I think that I’ll port that to my C# samples.</p> <ol start="9"> <li> <p>Instead of manually blocking threads, waiting with <em>Thread.sleep</em> to ensure the EventStoreDB subscription restarted, I found a spring Retry mechanism (<a href="https://docs.spring.io/spring-batch/docs/current/reference/html/retry.html">https://docs.spring.io/spring-batch/docs/current/reference/html/retry.html</a>). I used it to build a retry policy: <a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/samples/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/subscriptions/EventStoreDBSubscriptionToAll.java#L63">EventStoreDBSubscriptionToAll.java</a>. Again, throwing away the handcrafted code helped me improve the solution.</p> </li> <li> <p>I have also used Spring Boot Retry to implement <a href="/en/long_polling_and_eventual_consistency/">long-polling</a>. It is a technique that allows us to simulate the synchronisation of the API with eventual consistency at the data recording level. The If-None-Match header (<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests">https://developer.mozilla.org/en-US/docs/Web/HTTP/Conditional_requests</a>) can help with this. See more in an example: <a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/samples/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/ShoppingCartService.java">ShoppingCartService.java</a>.</p> </li> <li> <p>I also added Log4J instead of <em>System.out.println</em> for logs. Yes, Log4J. By the way, I believe that the famous Log4J security issue was not a Log4J problem. The problem would not reach this scale if companies (especially big corporations) had an appropriate dependency management strategy. Anyone can be breached, primarily such a popular tool, but we must be prepared for it as architects.</p> </li> <li> <p>I also added a Continuous Integration process using Docker images for EventStoreDB and Postgres to run end-to-end integration tests. <a href="/en/i_tested_on_production/">We don’t live in caves anymore.</a> You can run tests on real databases. See more: <a href="https://github.com/oskardudycz/EventSourcing.JVM/blob/main/.github/workflows/samples_event-sourcing-esdb-simple.yml">samples_event-sourcing-esdb-simple.yml</a>.</p> </li> </ol> <p>Finally, some numbers:</p> <ul> <li>143 comments in discussions in PR,</li> <li>47 commits,</li> <li>I changed 80 files,</li> <li>3906 lines of code.</li> </ul> <p>It’s been a long way! But it was worth it, and thanks to the community’s help, I eventually came up with a much sounder solution. Plus, I was able to share with you what I have learned. I hope that it may save you some mistakes. You don’t have to learn on your own constantly!</p> <p>Cheers.</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Small rant about the Software Design]]>https://event-driven.io/en/small_rant_about_software_design/https://event-driven.io/en/small_rant_about_software_design/<p>Some time ago, I listened to an enlightening episode of Scott Hanselman’s podcast on how to teach computer science. The guest was Maria Naggaga - Senior Program Manager at Microsoft. Among others, she told about why in recent versions of .NET they insist on making it lighter and more straightforward. I highly recommend this episode not only to .NET programmers because it explains well how to look at the product we produce through the eyes of the user. You can listen it here: <a href="https://hanselminutes.com/797/new-ways-to-teach-computer-science-with-maria-naggaga">https://hanselminutes.com/797/new-ways-to-teach-computer-science-with-maria-naggaga</a>.</p> <p>By product, I mean not only the software we produce. Not only the UI on which the target user will work. I also mean our colleagues who will use the classes, API or architecture we have designed.</p> <p>I think I told you this before, but I did architecture that I was very proud of. I thought it was a solid “enterprise” architecture. My perspective changed when, during one of the team-building meetings, a coworker told me that he did not understand anything about it at the beginning. But after a few months, when he understood it, it was great. Even though the intention was to compliment me, it was not the best thing I wanted to hear. I realised that upon reflection.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 450px; height: auto" > <a class="gatsby-resp-image-link" href="/static/7408f6899162bd0fdbb0a0f9ecdacda3/cf84a/2022-03-23-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 100%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsTAAALEwEAmpwYAAACw0lEQVQ4y21U60/aUBzlL1/ioku2ZSYzy+Y+zGyLug9L9sG4kfhE5hOGCIpagULp675723v7oAgscDdUQnK+tL2np+f8fqeZbsS6EYslYdj0qSM49JkrOOTUCTwQBlhwyLBJ0QgEmQxbSUgVK9ONWBLS4b0snBwc5Xe0m6per11Viq6pH+Z3Cif5WrUkOIokjQRRUMwH8n3XN/SbWqVYKZ81tOr1ZanT0mqXpcrFmXZdjgTuJV4aK7Bp8qAnquXCefGwWb86+r13dpxDbns4TAf9cHAv09ibEB7jQdm19ObdNbBbjqUbuuaYTewaYYAmR/8re0/IKjACOwR2MDAQaCPXQK4BnRYGHQJMiiw6foSBQaD5JDCFJKSxJNJHsSRJSBXikAaeC+wmZ67SmDCfkGNJOLEjgTl11Ilewq1OvXCWL5wcaDeVWJIZnpUf4QHBYT8NpI8CDyh7wG7t72Ur5dPjwz3g6P00mKGcxl4YYOQanDoYGD4DKpgwQK7V8Kmzufljdzc7mElWngPPDQOcSBoGI/FuxDxiOWaDwE7l/HTt29fbm4tBKqYDU8rSh72Eq8vAc2NJdrZ/lv6c2Ea9Vi1mf23kclszPbNIEDG2moT/3tVL+MbG9zeLrzWtcnq8v772ZWc3a5uNftdX4pnHO8CpEwmsopI+lD5KIpbLbc09fzb/Yu7t0uLq6oreqE1iyzz+jHG3LLX6ggPpwySk0G6vfPq4vPzu5auF+YW5q2px2BMzyN2YxSHlxPHwqJtp7LmW3qpfY2CYxt2H90vr65/zue1hP1Izz0zNPY1Hu5HGfGyehoIIHxDYMVra3W21VDwMPDhZ76n1VCDdiKr2JZJEoykgThzJIUNmEs2Ys0eRBZ02ctvQaak5CQ6g3RqXpI2cNrB0Ap707KFVDFmM2AxZHrHHNRr9dxi2Rzex9RiTJfkL0ThD35AigtkAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/7408f6899162bd0fdbb0a0f9ecdacda3/cf84a/2022-03-23-cover.png" srcset="/static/7408f6899162bd0fdbb0a0f9ecdacda3/36ca5/2022-03-23-cover.png 200w, /static/7408f6899162bd0fdbb0a0f9ecdacda3/a3397/2022-03-23-cover.png 400w, /static/7408f6899162bd0fdbb0a0f9ecdacda3/cf84a/2022-03-23-cover.png 450w" sizes="(max-width: 450px) 100vw, 450px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Unfortunately, our design often looks like the picture above. We throw a do-this-and-do-that a couple of times and then goes the rest. However, this is not an effective way to teach others about our architecture, API or product. We should enable users to learn about more advanced uses and concepts gradually. Until they need them, we should not force them to do so. Learning to draw a damned owl should look like this:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/0a2f2b80797c77c727e798f35059156d/332ff/2022-03-23-owl.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 69%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAIAAACgpqunAAAACXBIWXMAAAsTAAALEwEAmpwYAAACUElEQVQozy2R2U7zMBCF8/7iAXgOhOCSCxQJoaohRVDUkGZrEi+xHcdLnMXx/7fmuxqPdXw8ZwIIIUKIEMIYa9u26zpCCAAAY0wpBQCQGxBC32GM/fz8/P7+EkICSum2beu6KqW80hdd1ymlqqoqy7Kua4RQHMdlWWKM7+7u7u/vMcYBIcQ5t66rlNKLtdZeZq1N0zQMwzRNMcavr69pmhZF8fT09Pz8DAAIpJTjDWstIWQYBufcMAyUUmvtOI5FUczzbIypqkrfOB6Pp9OJMRb4MZxzxpimaSCE8zznee6d27bNskxr3XVdHMeU0mEY3t7eEEJKqcAY498TQqzrCiFkjPV9f7lcKKWEkLIs/ThJkvh6t9uVZWmMCaZpGsdRKcU537aNEMI5d84ppeq6HsdRaw0AkFKu65rneVEUAADG2DiOwTzPUkqtNed8nueu6/q+X5aFENK2LeccANA0jT9eLpcoisIw9P+9Oltrt20TQmzbZq09n895njPGyrJMkoRzjhBKkgTdeHl5ybJsWRYhxFUshGCMaa39kFrruq4hhD5qCKEx5nw+E0KklFEUAQCcc5zz66oAAFVVSSl3u9339zel9HA4xHGMMY6i6Ovrq2ma9/f34/FYVdXj4+N+v7fW9n1/XVWWZZRS55y11jlHKZ2mSWsNIVyWRWudJMmyLIyx/X6PEPr4+DDG/Dl7rLXLjWmaMMac82EYPj8/syzDGB8Oh9PpVBTFw8NDGIbeI/BRSyk55/0NzrkQwnd8+Eqpvu+FEOM4Yoz91f/A/gG1CgHbbs7ldQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/0a2f2b80797c77c727e798f35059156d/a331c/2022-03-23-owl.png" srcset="/static/0a2f2b80797c77c727e798f35059156d/36ca5/2022-03-23-owl.png 200w, /static/0a2f2b80797c77c727e798f35059156d/a3397/2022-03-23-owl.png 400w, /static/0a2f2b80797c77c727e798f35059156d/a331c/2022-03-23-owl.png 800w, /static/0a2f2b80797c77c727e798f35059156d/332ff/2022-03-23-owl.png 1000w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Development experience should not be optimised for edge cases or people doing something wrong. We often avoid simplifying code or architecture because we are afraid someone will abuse it. We require a much larger entry threshold from the user and understand everything simultaneously. We do not provide the appropriate default behaviour. Even on a microscale, we rarely think about giving the parameters of our methods meaningful default values.</p> <p>Of course, we shouldn’t prevent anyone from comfortably using advanced concepts or <a href="https://www.youtube.com/watch?v=7nqcL0mjMjw">living on the edge</a>. Yet, enabling advanced scenarios must not be at the expense of accessibility. We should not do this at the cost of users satisfied with the default behaviour. There are simple techniques to help with that, e.g. grouping advanced features into dedicated group/package/module/section. By doing that, we’re not “poluting” our regular approach and still enable more advanced people to be efficient.</p> <p><a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">Having decent documentation is always a good thing.</a> Yet, it’s not easy to create such, and even if we manage to have one, it’s not easy to push people to use it. It’s more effective to provide accessible API and document advanced cases than the other way round. Advanced people facing an edge case are usually much more motivated to find the solution for their use case. That’s why optimising for accessibility is more sustainable in the long term.</p> <p>Each step new users have to take to use your tool may be the last one. The single <em>“ah, you just have to do this”</em> is no problem, but if we stack too many of them together, it won’t be a satisfactory user experience.</p> <p>Design should be like a joke. The more you need to explain it, the worse it gets.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[Introduction to Event Sourcing - Self Paced Kit]]>https://event-driven.io/en/introduction_to_event_sourcing/https://event-driven.io/en/introduction_to_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a722dd0107586c5dda02eb07f561f1c4/d2429/2022-03-16-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AKeLPbSCSLKAWKWBYrSeir2wo6WPfu7VvefPsczKwLq8tJ+ZiaCIacuaZN2cVuOfV9iXU9OXVdifX9ifXAC+m0fdrV/9v4j0tHrpqnHXnGarcD/InXv34svNuqCqqZ6QkomHjYxaXl9pXU+ceFHJjk7glkbYkkXTjkQAZE0UdVgm/8uQ/Mqd/cuZ/8yV/bx804ZF0rae1dTOw7mrw7qvmJOKcF9LWVVRPUJHRUZEcF5G2JdK7qRLAGlIB3tZGfrDhvjDk/3GkO/Hn93GrevFoN/SxtHT0ru3s4yEfoZZLcl7K8OBPrZ8QYxkPY9lOt6SQOWaRwCCVgCRagDdoV73vIr9voHEl3JiWliLd2uPdGJYRDocDwpLJgjEfSvjmDzsok7xplHwokzsm0XcjTrThDMAs3pGk2wYzK087LFr+bd7zJBcZjEInlYYmFMXgUoaoWMnzoU14JU/6qRU8a9h8rFk87Jj8Kxc76hU555IAPS1dNybYLCNWNvIkvDEke+0edyVTOOXSOufUfGlVvCnV+ylWu6pYPGvZvGuZPKxZ/GtYfCtYO6pWOqjTwDmp2n0uHzYllmog125noHqsnv3wYbvtHfsrW3npmTppl/qpVzopV7tpVvvqF7vp13up17splzppFjknVAA5qVl6aps9LV10IxLvHUy6qtq87uA87yA9Lp79Lp667Bw6qhj16Jr3ZtY4plQ35VK45pO551M5ZtK45tJAOKiY+amaOmpa+2ubeypY+yraPC1dfK2duu1fd2reM2baciUXsCWbOKiX9+YUtiSUNiUT8yJQ8qEO8uENwDWllbdoGLenV7hn1/mp2brq2nio2TNmGXLjlXLjVO0gVPBgkTTmFzTmFbnoFDdlUjQhj/IgDy2cDSmYyzinYYaDhKyEQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/a722dd0107586c5dda02eb07f561f1c4/a331c/2022-03-16-cover.png" srcset="/static/a722dd0107586c5dda02eb07f561f1c4/36ca5/2022-03-16-cover.png 200w, /static/a722dd0107586c5dda02eb07f561f1c4/a3397/2022-03-16-cover.png 400w, /static/a722dd0107586c5dda02eb07f561f1c4/a331c/2022-03-16-cover.png 800w, /static/a722dd0107586c5dda02eb07f561f1c4/d2429/2022-03-16-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>For many developers, Event Sourcing is like a Nessie, most of them have heard of it, but not many have seen it.</strong> I was one of them. I started my journey with Event Sourcing over five years ago. Practical journey: Before that, I mostly read books, which shouldn’t count. Why? Because there were few of them showing how to apply Event Sourcing practically. When my colleague said, <em>“well, you’re doing a financial system then Event Sourcing sounds like a good match”</em>, I was curious. Then I was thrilled, as finally, I could use the pattern known from the books. I used <a href="https://martendb.io/">Marten</a> library. The first steps were hard. I was irritated. I started to get doubts if that was something for me.</p> <p><strong>I’m not the type of person to easily give up. Step by step, I understood more and more.</strong> After making mistakes and learning from them, I got a better vision. I noticed Marten has some missing pieces, so I sent pull requests. I also began interacting with users on the Gitter channel because they had the same struggles. Then I became a co-maintainer.</p> <p>That’s what I wrote 1.5 years ago in my post <a href="/en/revolution_now/">Revolution now!</a>. I wrote it when I joined <a href="https://www.eventstore.com/">EventStoreDB</a> as a Developer Advocate. That allowed me to have experience working with two different approaches to Event Sourcing and working with those two communities. There are also different shades, Domain-Driven, Object-Oriented, Functional techniques. You can do Event Sourcing in plenty of different ways. Yet, that’s not easy when you’re starting. The lack of resources motivated me to work on my sample <a href="https://github.com/oskardudycz/EventSourcing.NetCore">repository Event Sourcing in .NET</a> which grew into an extensive compendium. I also started to do spin-off repositories:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NodeJS">TypeScript and NodeJS</a></li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM">JVM based</a>.</li> </ul> <p><strong>One of the reasons is that I continue to learn to play in different ways, plus I’d like to cherish my dumbness, so I stay close to the people starting their journey.</strong> I believe that Event Sourcing is a pretty practical and straightforward concept. It helps build predictable applications closer to business. Nowadays, storage is cheap, and information is priceless. <a href="/en/never_lose_data_with_event_sourcing/">In Event Sourcing, no data is lost</a>. Yet, it takes work to learn. My goal is to make it more accessible.</p> <p><strong>Recently, I’ve been doing another private training, and as a side effect, I created a set of exercises together with suggested solutions that explain foundational concepts step by step:</strong></p> <ol> <li>Events definition.</li> <li>Getting State from events.</li> <li>Appending Events:</li> <li>Business logic (aggregates, command handlers, OOP vs Functional).</li> <li>Optimistic Concurrency.</li> <li>Projections (General approach, dealing with Idempotency and Eventual Consistency).</li> </ol> <p>That shows the practical aspects of using Event Sourcing with different toolings like Marten and EventStoreDB.</p> <p><strong>I decided to open-source them to allow people to do them as a self-paced kit. Get it here:</strong></p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Workshops/IntroductionToEventSourcing">C#</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/workshops/introduction-to-event-sourcing">Java</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.NodeJS/tree/main/workshops/introduction_to_event_sourcing">Node.js and TypeScript</a>.</li> </ul> <p>It took me two weeks of full focus, plus all the hours I spent in the past years. Of course, it’s not the same experience as attending the workshop. But it should be a decent starting point in your Event Sourcing journey. It should give you the tools to build on and play with your ideas and reduce the initial confusion.</p> <p><strong>If you need more than that and want full coverage with all nuances of the private workshop, check <a href="/en/training/">the training page</a>.</strong> Feel invited to contact me via <a href="mailto:[email protected]">email</a>.</p> <p>I plan to extend it and provide similar exercises in other languages when I find enough time. Contact me if you want to <a href="https://github.com/sponsors/oskardudycz">sponsor such an effort</a> and make that happen for your favourite tech stack.</p> <p><strong>If you like it, share it with your friends to benefit.</strong></p> <p>Drop me also a line with your thoughts!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[How to ensure uniqueness in Event Sourcing]]>https://event-driven.io/en/uniqueness-in-event-sourcing/https://event-driven.io/en/uniqueness-in-event-sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4a5511c7643805f5fc2bc4736aece6f4/d2429/2022-03-09-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACnklEQVQozwXBaVMSYQAA4P0V/oBqHA8K0EQQRIjlXBRYENh3uVZZdrnxdszxCBURFVAQAVMQBAQPPNKxzHL80EwzTV/7OT0P1NNl5PaYBFwg4GH9XKyPC3Q639j0uhqhOIzBuATkPmBHMF6QmA8k4ECClWBQgbGqFD+VW6GTq8dK8+Gw2iyf3Vx/fcqsJR32EJMpt1gmehmaXdhyCGNFGBRgcChEC2JDVQYacnChN5+YEKh+95jIFmvXt3tHperZRXNwEBhInhij6Hlu51BGbi1KwYkUHCnsxdetZ2xuSUmcavXNOeVxgAcVGs1Cvfn85+/9w+2UNRJh47eO0R8vz+HwDqcNyStsJbmlpnLUYUPmrTjJRMoi86UB/RJGauNCCNNTZh1pRAhUPWIXz963ChZYmrLZsrEYFbDQnMK6yxLmeySLIjdH06B5M5HekStU/7CqPp8RQjyGsrtNynrVz2yHwcD0FhPbiuz8+/VcKldE3YacwlYUafa4SFTpd6ojfcpjTDjXcJkeY8OVoADiM9WdrRK+2NbFQN5oL1s034Ibdzc/n2bnY/0szZEU1JWWusp2J0M3ZG6O/GTWQH9fVx34RGvaDojDHOpoV9CRarsgxNXum/HYACgD0xJujRL2tYTOG0f928axVW2oV1dr0b+Q5NLvhCpHCzZN7yAjFunuGmazBwFjyE2mvRPFsNgx5csGpku+YJ70pJzePcqf6rXW+KD03lT1uFbOP+GfPfxNYyc0QqYsRBLXz0X5qJtOjQYLC0rXpGaM9OVJaoekEiQVJ1xxWhygbCvjMs+kfpIOpmeD4SnfMkQ4kw53NoiHxw0fR315yrNPu1JefNVFp0ddCcK5RYzEALW73YdMD/nnpfZlGCcCGdqb9oSy/wGgABXsMRJLTAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4a5511c7643805f5fc2bc4736aece6f4/a331c/2022-03-09-cover.png" srcset="/static/4a5511c7643805f5fc2bc4736aece6f4/36ca5/2022-03-09-cover.png 200w, /static/4a5511c7643805f5fc2bc4736aece6f4/a3397/2022-03-09-cover.png 400w, /static/4a5511c7643805f5fc2bc4736aece6f4/a331c/2022-03-09-cover.png 800w, /static/4a5511c7643805f5fc2bc4736aece6f4/d2429/2022-03-09-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><em>“How do I ensure uniqueness? For example, a unique username or an invoice number.”</em> That’s usually one of the first questions I hear from someone starting the journey with Event Sourcing.</p> <p><strong>Uniqueness is an intriguing case in general.</strong> In my over fourteen years of experience, the requirement of uniqueness appeared to be usually a myth. When we get the uniqueness requirement, it often means something other than the uniqueness itself. <a href="/en/bring_me_problems_not_solutions/">Business often tries to bring us a solution, not a problem</a>. It’s always worth asking why is it needed and what problem that would solve. It usually turns out that the problem lies somewhere else, and you should approach it differently.</p> <p><strong>Nevertheless, we might not always have a choice or enough power to argue about uniqueness.</strong> What to do when we actually have to do it?</p> <p>Classically, we can use the unique index on the relational database. In Event Sourcing, we can also use it, as long as we use a relational implementation underneath. For example, Marten enables the following trick:</p> <ul> <li>we create an automatic snapshot that will store the current state of our aggregate (stream). This snapshot will be stored as a single record in the database.</li> <li>for such a snapshot, we can define a unique key on the fields. Marten stores the snapshot data in document form: <a href="https://www.postgresql.org/docs/current/datatype-json.html">JSON type</a> columns. Postgres (on top of which Marten is built) allows defining indexes on a JSON column.</li> </ul> <p><strong>We can mark the snapshot as <a href="https://martendb.io/events/projections/inline.html">inline</a> to be updated in the same transaction as the appended event.</strong> So if a snapshot is added/updated, the database key will ensure data uniqueness. Read more in <a href="/en/unique_constraint_in_marten_event_store/">Ensuring uniqueness in Marten event store</a>.</p> <p>Let’s be honest, however, that this is a pragmatic trick. It is not a “by the book” solution. Many Event Sourcing solutions do not provide such a possibility. For the most part, we have similar limitations as key/value databases.</p> <p>Keys and indexes are fun, but they limit performance, cause deadlocks, etc. So if you want to get the most out of the <a href="/en/relational_databases_are_event_stores/">event log</a> and its “append-only” characteristics, it’s worth considering other solutions.</p> <p>All event stores I know give the uniqueness guarantee for the stream identifier. A stream is an ordered collection of events recorded for a specific object, for instance: events of a given user. The identifier could be an e-mail or social number for such a case.</p> <p>Since the stream identifier is unique, by formatting it as <em>‘user- {e-mail}’</em>, we can easily enforce the e-mail uniqueness for all users.</p> <p>Oh well, but is it really easy? What if the user changes their e-mail? Or what if we say that e-mail should be unique only for active users? <a href="/en/gdpr_for_busy_developers">Or how to handle GDPR then?</a></p> <p>The first improvement is adding a hash function. It will allow adding new fields into unique constraints and anonymisation but won’t help us with e-mail changes.</p> <p><strong>The <em>Reservation pattern</em> comes to the rescue.</strong> When performing a business operation, first, we request a resource reservation: e.g. a unique e-mail value. Reservation should be durable and respected by concurrent resources. Typically it’s recorded in some durable storage. For instance, for key/value storage like Redis, we may use the unique resource id (e.g. user e-mail) as a key. The most important is that this storage should allow us to claim the resource with a unique constraint. The reservation can be synchronous or asynchronous (e.g. when it requires more business logic than just adding an entry in some database). We can continue our main business logic only when we get confirmation that the reservation was successful. Remember, we cannot just ask if something is unique. Such a query doesn’t give us any guarantees. Read more in <a href="/en/tell_dont_ask_how_to_keep_an_eye_on_boiling_milk/">Tell, don’t ask! Or, how to keep an eye on boiling milk</a>.</p> <p>With a reserved resource (e.g. user e-mail), we can run the rest of the business logic and store the results in our main data storage.</p> <p>How to handle the case when the user has changed the e-mail? As the first step, we reserve a new e-mail to ensure that it’s not used yet. Then we execute the business logic and finally send a request to release the reservation for an old e-mail. This can be compared to the good old concurrency pattern: <a href="https://en.wikipedia.org/wiki/Semaphore_(programming)">semaphore</a>.</p> <p>It doesn’t seem that complicated, but it can escalate. What to do when we reserve a resource, but the business logic crashes or the data fails to save? What if we change the e-mail, but releasing the reservation fails? We have a problem if our storage does not support transactionality (and a large part of key-value and event stores do not support it).</p> <p><strong>Here we come to the problems of distributed systems.</strong> I wrote more about it in my posts about <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox pattern</a> and <a href="/en/saga_process_manager_distributed_transactions/">Saga</a>. As always, it all depends on due diligence, risk management and potential consequences. The safest bet is to assume that everything can go wrong and have a <a href="/en/what_texting_ex_has_to_do_with_event_driven_design/">compensating action</a> up your sleeve. Such an operation could be triggered by a timer and cancel the reservation if it doesn’t get a confirmation event within the set period. Alternatively, we can add an administrative method for the manual release of the resource. Such things will rarely happen, but if they do, we would prefer not to send a new software version to correct the data with some migration. Read more in <a href="/en/no_it_can_never_happen/">No, it can never happen!</a>.</p> <p>As always, the scenario is simple until it’s not. It is always worth making sure what problem we’re trying to solve. Do we really need a unique constraint? We should analyse our options risks and make a pragmatic decision tailored to our business and technical design.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. <strong>Ukraine is still under brutal Russian invasion. A lot of Ukrainian people are hurt, without shelter and need help.</strong> 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 <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a>, <a href="https://savelife.in.ua/en/donate/">Ukraine humanitarian organisation</a> or <a href="https://www.gofundme.com/f/help-to-save-the-lives-of-civilians-in-a-war-zone">donate Ambulances for Ukraine</a>.</p><![CDATA[“I'm not interested in politics” is not actual anymore]]>https://event-driven.io/en/russian_invasion_of_ukraine/https://event-driven.io/en/russian_invasion_of_ukraine/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e8311cb7ac03ca6917d1f80ce7bb05fa/d2429/2022-02-30-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACe0lEQVQozx3OW0hTARzH8R/uEJH7n+NT0GNYRmRiJksswnBadpEMM9PQJNTZVUG8W+nExpaYbsvddUynntyZc84bDtsUr2HNgsKELkSWGIGV5VtM+Lx/v0BEOcJLEV6GfRXYX4WIGhy4j4P1ONSIw0pENSO6FTFaxBohacdRC2IsiLVC0oVj3cARFSIbEaVCdDNitZC0QaJHnBnxHTjeiZM8TjmR4ETiIJJGkDgKqRfJEzjtwxk/EPcUcQbEW3DChoQeSAUk9SPJjeQhpIzh3ATO+5A6hbRZpM0jfRFXlnA5gPQAMl4jJIUPOSuILrhFF0dEl8ZFGc+ZzEnm6gxzbYHJWWTyAsyNN0z+W6bgHVO4zMjeM0UrjGxb4QqQ40feAvKXIFvG7Q8o/ozSVZStoXwdlT9Ru4EHv1G3Cfk/yLfQsE2+hfq/qN/E7krX3johUtkreWKTthlTzdqMztas3pYCh+Km61GRWykbasofbr0+os/2mLMGTVkDhkzBkMa3S7t7wBv3jFq5eX7HshsfR/BpFF+8+ObD9ymszwT9mMNmAH9eBW28wK8FrPmw6sXXcaCnaaeg3jVoDHXqyaHnXBaO14c5zWETfZxf4KZd3JyHm3SFTbu5l+PsvIebHeAmHdywlZvo5qCpI4uS2lXiHrW4Tyt2GcQmFRmVrNNEAxaxv0881kVDNhq2k8dKDgMZH7OaRuo3Ea8lVN8V19wjWa5YXkaKSlLVUOUdMqpYm5r4Ngru6GjMToKJfWakXgOrUbD1FdRQRToFoaSQbuVRakpobjZVl7CKWraxnHRKtukhNZSTsprsarJr2I4W8vI0bCNBR61yqigONv4DrcUK4eLa1rcAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e8311cb7ac03ca6917d1f80ce7bb05fa/a331c/2022-02-30-cover.png" srcset="/static/e8311cb7ac03ca6917d1f80ce7bb05fa/36ca5/2022-02-30-cover.png 200w, /static/e8311cb7ac03ca6917d1f80ce7bb05fa/a3397/2022-02-30-cover.png 400w, /static/e8311cb7ac03ca6917d1f80ce7bb05fa/a331c/2022-02-30-cover.png 800w, /static/e8311cb7ac03ca6917d1f80ce7bb05fa/d2429/2022-02-30-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="https://www.youtube.com/watch?v=C2Pt-LnQ2po">War. War never changes…</a></p> <p>In IT, we’re living in a bubble. Compared to others we have good working conditions. We learned to complain about tiny things. <a href="https://github.com/dotnet/csharplang/discussions/5735">We’re running disputes on which framework is better or worse. We even do heated discussions around new language syntax.</a> It’s a gigantic bubble. Bubbles tend to burst.</p> <p>We believe that technology is objective. AI and Machine learning will serve us only good and won’t gather our biases, right? <a href="/en/computer_says_no_we_may_have_an_issue_with_ai_soon/">I’m afraid that’s not the case.</a></p> <p>Some of us <a href="https://www.theverge.com/2021/4/27/22406673/basecamp-political-speech-policy-controversy">believe that we can create a safe zone in our work without politics.</a> We think we’re building the World Wide Web without boundaries and national prejudices. Globalisation unifies us. You can buy iPhones around the world.</p> <p>Unfortunately, it’s only wishful thinking. It’s easier to build a bubble and ignore what’s outside. It’s comfortable to forget that world is changing, and those changes will get us all.</p> <p>Last week brutal attack from Russia came to Ukraine. A country that, step by step, was evolving and building its independence painstakingly. They have a strong IT industry with a lot of <a href="https://twitter.com/biofsphere/status/1498085717628624898">talented people</a>. It’s hard to give any pragmatic reason for such a cruel move besides creating war games to hide internal Russian issues. Unfortunately, this is not a game. It’s also not a movie, even though that may look like from the comfortable couch. This madness needs to stop, and we need to do it now. Living in Poland, I know that we can be next if Putin won’t be stopped.</p> <p>Of course, life has different shades of grey. For instance, our Polish-Ukraine relations went through extreme ups and downs. My grandparents were born where now is Ukraine and previously was Polish territory. Ukraine was on the wrong side of WWII. My family suffered. Yet, do I have doubts that we should help Ukraine? No, I’m 100% sure that we should do everything we can to help as much as possible. Sometimes the only way to fight with the bully is to “outbully” them.</p> <p>How can you help?</p> <ul> <li>You can make donations, even without getting up from your couch. Most humanitarian organisations are gathering help, e.g. <a href="https://www.icrc.org/en/donate/ukraine">Red Cross</a> or <a href="https://savelife.in.ua/en/donate/">Ukrainian organisation</a>. Refugees are lacking even basic stuff like food, blankets, etc. You can find organisations gathering all of that and bringing it to them.</li> <li>A patient drop cuts through the rock. Put pressure on your company (or those that you’re a customer) to drop any connection to Russian money. You can always ask how they’re planning to react and if they’re aligned with sanctions. Don’t be afraid to stand up.</li> <li>If you know Ukrainian people, ask if you could help. If not, then ask your friends if they know someone. We have to be united.</li> </ul> <p>It will be a long run. Ukraine will need continuous help for a long time. So prepare yourself.</p> <p>It’s also important to watch carefully, as our world is changing. I wrote some time ago that the split for IT and business is obsolete, as now IT is business. The same applies to war. IT is an important aspect here. Weapons are already filled with chips and software. Two hundred years ago, Edward Bulwer-Lytton wrote that “The pen is mightier than the sword”. IT may be already mightier than a rocket. <a href="https://en.wikipedia.org/wiki/Facebook%E2%80%93Cambridge_Analytica_data_scandal">Cambridge Analytica already impacted the USA president’s elections and Brexit.</a> And that’s just a proven case. Have you heard about the geopolitics issue with processors production? Do you remember that <a href="https://blog.cloudflare.com/a-byzantine-failure-in-the-real-world/">a single company (Cloudflare) created that Internet was practically not working for a few hours</a>? Luckily <a href="https://t.co/Kwy96lU2Kw">Anonymous decided to be on the right side</a>, but will they always be like that? Beware of the fake news and misinformation. They’re also weapons now. Don’t reshare what you see on the Internet without verification, as this may help Russians.</p> <p>And lastly, if you don’t stand up, then who will? We don’t always can do much, I’m also feeling powerless, but even a small impact is an impact.</p> <p>Last but not least.</p> <p>Russia, shame on you! The world will remember that.</p> <p>Ukraine, stay strong! Слава Україні!</p> <p>Oskar</p><![CDATA[15 tips on how to run meetings effectively]]>https://event-driven.io/en/fifteen_tips_on_how_to_run_meetings_effectively/https://event-driven.io/en/fifteen_tips_on_how_to_run_meetings_effectively/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/15d62e8adb3334bf659b38d17526ed06/d2429/2022-02-23-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABwElEQVQoz12NTWsUQRCG+2cIHgVB8GROOSviF+iKIF5kES/BU/DsRURBERRRFNSAIUIEQXLV5OBNI4g3hSyI2Z3q6a7qrv6a2VkRpGdmVYSHprvred8SxkXjUo/PEEefprtSc2SfauJILpNN3zudL+bJvoI4cUiFiotHVw+fWZsoz6EynP4u+AdhXGW4HbegialuPn4p9hx4sPfg3Y13O81shiZlzf3fItBWLQltXos2WZ++F3x2uHrq3I1nLz80P38pisgVzU3K9zasbGxJPSZqE0ubrlzfXBw8Wb61GeuZxKBs1HNNt6BNojTxD10RYKhn02uPto9cfDFYWhuXGrmSFHrNZjpTSIqSgjRRmiBNKG0CCmlar2x8O3F5fbh87+tohK4BCr1GrZaLgijQAwVAD+iAfGkiULA+vv8M+489P3T65putHZeqscrTDmm8pIzo3gW6Al3+NQEoaOtHY7dw8uG+hat3Vj5VTb1bWiAHlDVAL9FJzGE30ZzDmiHngzQeyCmujp9/KsSl4dKrWFc/gEAzaC60Bc0SMwKQC2VlexaaZa73E8Wunt6+/3Zw4fH6621l3FiSRAutKZG7/G+6tz80q0jp6wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/15d62e8adb3334bf659b38d17526ed06/a331c/2022-02-23-cover.png" srcset="/static/15d62e8adb3334bf659b38d17526ed06/36ca5/2022-02-23-cover.png 200w, /static/15d62e8adb3334bf659b38d17526ed06/a3397/2022-02-23-cover.png 400w, /static/15d62e8adb3334bf659b38d17526ed06/a331c/2022-02-23-cover.png 800w, /static/15d62e8adb3334bf659b38d17526ed06/d2429/2022-02-23-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>It’s always worth talking things through! But is it worth meeting? During my career, I have seen many projects and companies where the “meeting culture” was killing employee performance. “The more meetings, the better the job goes.” More than once, I have commented so sarcastically on a new meeting that should have been an e-mail. Today I decided to share a set of tips that can help you eliminate or at least reduce this problem.</p> <ol> <li> <p><strong>Meetings are great for nuanced discussions or agreeing on different perspectives, brainstorming, etc.</strong> Especially when you see faces and emotions, so turn on the camera, don’t be shy. If you’re in a mess, no worries! You can change the background in most current video conferencing apps.</p> </li> <li> <p><strong>If you need to, you can skip meetings.</strong> Really. It’s also okay if someone can’t attend your meeting and refuses you.</p> </li> <li> <p><strong>Prefer a written asynchronous form over meetings to share your status.</strong></p> </li> <li> <p><strong>Avoid scheduled, repetitive meetings if they are not needed.</strong> If you really need a status meeting, make sure it’s as infrequent as possible, focused, and short. Separate status meetings from problem-solving. Even if you feel a status meeting is needed, ask others to share your perspective. You may find that you are the only one who finds such a meeting useful.</p> </li> <li> <p><strong>Avoid meetings that could be a status update via message/document/e-mail on Slack.</strong> The most important thing is to know the blocking impediments and resolve them. The status update can usually wait. Besides, the status itself is generally of minor importance. What is essential is to know about emerging problems or new risks. They have to be solved. Why do you need the information that “it’s going okay”, “I’m still working on the same as yesterday” if it doesn’t mean a deviation from the original plan?</p> </li> <li> <p><strong>If you need to solve problems, arrange a dedicated meeting with interested parties.</strong> Don’t solve specific problems with lots of uninterested people. Again, a status meeting is not a problem-solving meeting. It’s a meeting to find obstacles.</p> </li> <li> <p><strong>Make sure you schedule meetings in advance, respecting time zones.</strong> Don’t postpone or cancel appointments right before them without good reason.</p> </li> <li> <p><strong>Make sure the meeting has a clear agenda and purpose.</strong> Determine who is required or optional. Please state why this meeting is necessary. This will give the invitee information whether they need to attend the meeting or not. Do you remember? It should be okay to miss a meeting.</p> </li> <li> <p><strong>If the meeting has no agenda, anyone can decline the meeting right away.</strong></p> </li> <li> <p><strong>Prepare a document with an agenda/topic for discussion.</strong> Ideally, if it contains the context of the meeting, the proposal(s) will be the basis for discussion. It is much more effective to discuss something based on a suggestion than to invent something from scratch during the meeting. The document should be concise and focused on the merits. Suggested ways to share documents are: Pull Request with a markdown file, Google Docs, Github Discussion or Issues.</p> </li> <li> <p><strong>Begin the meeting by reading the written agenda/problem (this could be reading all or someone referring it).</strong> This way, you make sure that there is a common understanding of what is discussed.</p> </li> <li> <p><strong>Concentrate on the purpose of the meeting.</strong> If other ideas came up during the meeting, deal with them separately or at the end of the session so that uninterested people can leave them.</p> </li> <li> <p><strong>Make sure that one of the participants is taking notes regularly on the discussed document during the meeting.</strong> In this way, we enable other interested parties who could not join to see the discussion results. Doing this on an ongoing basis will avoid the problem that writing a “minute” after the meeting is tedious and overlooked due to other priorities. Taking notes enables people to skip the meeting. Joining meetings because there is no other way to know the outcome is one of the main reasons people are in meetings just in case. Usually, there is nothing essential, and people sit there and don’t listen, doing something else in the background. It also causes meetings to be dragged and inefficient.</p> </li> <li> <p><strong>Make sure there are actionable decisions from the discussion.</strong> If there are no decisions, then specific actions to get them later. Assign specific people to them if you don’t want the matter to blur as “I thought Jim would take care of it”.</p> </li> <li> <p><strong>Finally, a controversial piece of advice: stop sharing calendars.</strong> It will make it difficult to create meetings based on “I saw that Jane is available, so I will throw this meeting on her”. Making undesirable effects more difficult is often sufficient to prevent them. People will think two or three times before writing to multiple people and organizing a shared appointment. Perhaps this will be enough motivation to write down your thoughts and prevent the meeting.</p> </li> </ol> <p>Don’t get me wrong, meetings are beneficial. If they are well-organized, they can solve in fifteen minutes what would be impossible to solve in the communicator chat for hours. They’re also less argument-prone. When we see ourselves, we hear ourselves, our level of empathy increases. Unfortunately, most projects and organizations have a shortage of valuable meetings and too many unnecessary ones. The worst thing is that a more significant number of meetings often affect the most important people: leaders, senior programmers, architects, etc. They become less accessible, making them limp about what they should do, design and management. This, in turn, has a significant impact on the entire team. As a leader, one of the necessary things is to be assertive and be aware of it. Don’t create bottlenecks in that way.</p> <p>In this article, I also appeal for respect for others, preparation and I am offering tools that can help in this. Here are some more links:</p> <ul> <li><a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">How to successfully do documentation without a maintenance burden?</a> - my article on how to conduct a discussion using documents (ADR, RFC and other essential terms),</li> <li><a href="https://www.justingarrison.com/blog/2021-03-15-the-document-culture-of-amazon/">The Document Culture of Amazon</a> - how Amazon introduced it at home.</li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[Immutable Value Objects are simpler and more useful than you think!]]>https://event-driven.io/en/immutable_value_objects/https://event-driven.io/en/immutable_value_objects/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/09f2e82a600cba60c8efc8c46e9f56ec/d2429/2022-02-16-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACj0lEQVQozwXBWVMSAQAA4O2hyRiNMcVSM/DiZllZjkSDrMn+SE+NHQ95grKD3NdyCImSog4j9eBDDz00xsAu7H2xKPZz+j6AQkE265AqDnr3Pheb4zMaJq3hQjNMYrbx6R6dNhKHVjI6ywYnhDLMJMZEFJRKTiyk5hAFwB+vYNtDVNLElax83sQjk3xmnt0bYBMGPL+AhZ6RgXG+bGPSajak41EHGZ4UImPCMdyrOoF2CiZTLiFnprZVYg6i95VM0sijNiIwSu4ouQMrX9Jz4TkJhdoBFbM7Kpfd8rmXjAz+u7ABYhnCdpRM8SVTgpiwlg0a+PhE26/kcja+CLK+cSlp5LKzQnqaj5ulAswiQzffIKkK92ovAOn8rVB9Q8aM1+tGGlHjm4+ZlIsrwa2NEXLfIJYsXFJLBOxiwUX4Hnbz1t6pWz6ySPGR258egClOUZk5Ove6GTThCETFXI3PisbGcxqFqSTYWNdjfvP1xwE+oZcrHjapJTcf9Kv23gUon4KAVFsVz1fJ+Dwdtbe+PGr51Z24l4iDjTUVFnD+XRvGt7RUxM5krdjWoJhfuClbupmZfl4n1BwAmxwUimauYKVQSycMUVHrn/cDeNBGJQytgBHzmwlkpr2pELJOseym0hoppZYrcP/ELFVNwN0FJH23UBkbf7jc3FBgscV2xN4J6Rtfp9uIrul7QoXM3YqX2HvKISrhxNU9c/CH2ru6m7k0AtzBVLfqZVENVV4hDt61o+DvD6pOchkP6XC/nk648L0pClHJR4u9qlsqqPs1j/jDfnu1JF+5AenSzZ65pbNltmC93h4m0SUi62jGFpqJVy3fTCcwwZU93YoL942IqKFfh2+uPHLd0f3lpevT/wFWEkoTPKQWGAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/09f2e82a600cba60c8efc8c46e9f56ec/a331c/2022-02-16-cover.png" srcset="/static/09f2e82a600cba60c8efc8c46e9f56ec/36ca5/2022-02-16-cover.png 200w, /static/09f2e82a600cba60c8efc8c46e9f56ec/a3397/2022-02-16-cover.png 400w, /static/09f2e82a600cba60c8efc8c46e9f56ec/a331c/2022-02-16-cover.png 800w, /static/09f2e82a600cba60c8efc8c46e9f56ec/d2429/2022-02-16-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I love proper typing. But what does that even mean? I could say that this is a types’ structure defined in a self-explanatory way. But that’d be clichéd, wouldn’t it? That’s why I won’t say that.</p> <p>Before I start coding, I’m asking myself (<a href="/en/bring_me_problems_not_solutions/">or others</a>): <em>“what is the business use-case, what my code is supposed to do?”</em> That’s a necessary starting point of providing the types’ structure. It’s best if our code speaks the same language as a business. For instance, <em>SendInvoice</em> is more straightforward than <em>OnSaveButtonClick</em>. You’re also grasping a business use case by looking at the code. Even if it is a pure infrastructure code, I always try to think about how to use it. So I’m putting myself as another programmer, a potential class user. Knowing what the type should be doing helps identify an even more important thing: what the type should not do. The fewer, the better. Our goal should not be to create a class that can do everything. You wouldn’t like to build the new Skynet that decided that humans are redundant, right? The less the class does, the more precise its intended usage is.</p> <p>Therefore, I try to avoid inheritance and generic code. Of course, there are justified situations when it is worth using these mechanisms to not follow the Copypaste Principle. Nevertheless, it is usually better to simply wrap up the logic and expose a specialized set of behaviours instead of inheriting class with all the baggage.</p> <p>Take, for example, shopping cart requirements. If we add a product item to it, then:</p> <ul> <li>when the product is not yet available, add an entry with the product item’s quantity and price,</li> <li>when there is already a product with a given price, simply increase its quantity.</li> </ul> <p>Similarly, when we remove a product item from the shopping cart:</p> <ul> <li>you can only remove the existing product,</li> <li>when we remove less than the current product’s quantity, we just reduce it,</li> <li>when we delete all products items, we also delete the whole entry.</li> </ul> <p>If we modelled product items with a regular list, we would give the user too much choice. With great power comes great responsibility. And in this case, also a burden. We need to know the whole business logic to use such structured code. We’d need to also add validation before each call to make sure that we’re doing something acceptable. That means a lot of code duplication. The more mechanical repetition, the higher risk of a dummy mistake.</p> <p>Therefore, we can try a different way, like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ProductItemsList</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> items<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">ProductItemsList</span><span class="token punctuation">(</span><span class="token class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> items<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>items <span class="token operator">=</span> items<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProductItemsList</span> <span class="token function">Add</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> clone <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span>items<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> currentProductItem <span class="token operator">=</span> <span class="token function">Find</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> clone<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">else</span> clone<span class="token punctuation">[</span>clone<span class="token punctuation">.</span><span class="token function">IndexOf</span><span class="token punctuation">(</span>currentProductItem<span class="token punctuation">)</span><span class="token punctuation">]</span> <span class="token operator">=</span> currentProductItem<span class="token punctuation">.</span><span class="token function">MergeWith</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemsList</span><span class="token punctuation">(</span>clone<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProductItemsList</span> <span class="token function">Remove</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> clone <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span>items<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> currentProductItem <span class="token operator">=</span> <span class="token function">Find</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>currentProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token string">"Product item wasn't found"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> clone<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>currentProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductItemsList</span><span class="token punctuation">(</span>clone<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductItemsList</span> <span class="token function">Empty</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">List</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> <span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token interpolation-string"><span class="token string">$"[</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp"><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span><span class="token string">", "</span><span class="token punctuation">,</span> items<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span></span><span class="token punctuation">}</span></span><span class="token string">]"</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Not too much code, but a lot of stuff is going on.</p> <p>First, there are no interfaces or abstract classes. Why would we need them if we assume there will be exactly one implementation of the product items list? From the perspective of developers using it, they should think of it as a more sophisticated list. They should ignore the implementation details and treat it as a library class. Thanks to that, we do not have to duplicate the business logic, validations etc. We can unit test our class implementation and not repeat those tests everywhere we use it. We can assume that it just works (as we do for any other type from the external package).</p> <p>Another interesting fact is that our collection is immutable. When we add or remove an item, we always return a new list instance. What do we gain from doing that? Predictability and reducing multi-thread access issues. We do not have to worry about someone accidentally changing it in another thread. Of course, there is a performance penalty for this. If we need über-performant code, then we should remember that. Still, most of the time, that’s not an issue. How many items can a shopping cart have?</p> <p>In the exercise above, we created a simple Value Object. It is an immutable object that is represented by the elements it contains. It can have behaviours and be composed of other value objects (you may have already noticed <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Sample/EventStoreDB/Simple/ECommerce/ShoppingCarts/ProductItems/PricedProductItem.cs">PricedProductItem</a>. It is also a Value Object).</p> <p>Thanks to the simple composition, we get a set of predictable, precise types. We ensure that they only accept the correct data and performs the proper logic. Thus, we reduce the need to have a multitude of tests testing an imaginary interface’s implementation. That reduces the development and maintenance and maintenance time.</p> <p>To see the full implementation, check the code in my sample repository: <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Sample/EventStoreDB/Simple/ECommerce/ShoppingCarts/ProductItems/ProductItemsList.cs">ProductItemsList.cs</a>.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[How to quickly scale a legacy monolith?]]>https://event-driven.io/en/how_to_quickly_scale_a_legacy_monolith/https://event-driven.io/en/how_to_quickly_scale_a_legacy_monolith/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/1220ba7841cb397ab995983467707cf3/d2429/2022-02-09-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AN3Y3ujl5dzIYuTQTvruYN/HS+PKR/bjS+XGPuzZS9O6QfDeVunaVuDZVWx2Ny1HKpeSQePXWOLRVaCMMwDHvJDe1qnf3KrY067Z1LHx6Iz26mjx6Y3p4Izm34/16G/e2VDL1Eejuz9tgjeKkUhufzm0zErEyUugijIAsJ9i2c6K2tbI0s260MzN5N6o6OKn1NDXycfEzsnH49uJwMc4tMxCm7FAtslGm6ZLts9OlalDwM5KnZkxALSkZM65VfHoePXtjN3Zy8jEyefgjvbrauHWbVxxRICKQdfmRYupPa+6P9vrSsLaSd/rUI+bPKO3RpGbNQCzo1ng02D47mvs4mr573bo33/482n//WCyw2FWiXGEoFS5yTtpeTW1wz/I30PC2Ea800dqeDyCiz2InToAj3tEppFE6Nte9+1m/vJm++9g39RXw8Zbg76Gd7aKqsdVt8k8gD9SoGJWoHVSl3xLkGBRozZsrp1QqLs8AJSDSKKJPtrJXP30a//5bvrxaoePWH+UZ4G7gnCxg63WYbi3Rr02fqUgcJQdZpsjaLAeeb9kcL+zVaOzPgDKvWPn11398mvt42LEt1Xi3mimvIK00YyDu4NuWWKnqEnBuUqma2CFPFGzoE7axV6DWUiXOWCpjFOXrzsArpxd4c5Z//hq5NZ3t6mc0sOEsLtshpRqcZpxhnJ1opBCt6lKizJXtYtZ//9o//hs+/lmv59amV1af1RJAMq9brOhWMm1et7PhbOWksOpnLNTa3wxU4BqZ3WneZtvVrJqXp4ga6VNXfTnY/zuaf/5bMOVYZkuZoIoVgDb0XnJtHrBpp6xeninV1usd4CeR1yzsmSXUWmVcGXMyE2rqUuSXFKhVWvXyGL//Wvl2WOxoU+PpEd3fkXRj4u8tFxW5gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/1220ba7841cb397ab995983467707cf3/a331c/2022-02-09-cover.png" srcset="/static/1220ba7841cb397ab995983467707cf3/36ca5/2022-02-09-cover.png 200w, /static/1220ba7841cb397ab995983467707cf3/a3397/2022-02-09-cover.png 400w, /static/1220ba7841cb397ab995983467707cf3/a331c/2022-02-09-cover.png 800w, /static/1220ba7841cb397ab995983467707cf3/d2429/2022-02-09-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Working with a legacy monolith is not easy. For years we learned how to tame that beast gently. We nurtured it and tried not to break it. We even may enjoy that carefully crafted ecosystem. Being a legacy doesn’t have to be a bad thing. It usually means that you have paying clients. That’s something that a lot of startups cannot say about themselves. Working with monolith gives as also a lot of stories to talk about with a glass of beer. Of course, those are primarily complaints, but at least we’re talking.</p> <p><strong>The problem begins when it turns out that we have tangled our code so much that the system ceases to be efficient.</strong> Our business is doing well, the scale has grown, but our code is lagging. We can no longer handle needed traffic, especially when peaks in demand like Black Friday happen. Our system suddenly fails and cannot handle the best time to get deals. Sales related modules are especially vulnerable to that.</p> <p>It quickly turns out that our tangled code slows processing. Rewriting it is not an option. Even if we get permission, it will take a long time, and time is already running out for us. Maybe we could break down the monolith into separate services? Nope, it will also take a lot of time.</p> <p><strong>Extracting bounded contexts, CQRS - everything is great, but you have to do it wisely to make it help - you can’t be too quick. We’re developers. We’re cutting the Gordian Knot every day. Still, if we’re asked for a miracle, we’ll ask to wait a bit.</strong></p> <p>So how do you deal with it if the time is running out?</p> <p>Usually, the database is not the problem. Relational databases are designed for heavy loads. As long as we haven’t done anything foolish like making dozens of stored procedures or having constant dead-locks, they should be able to handle it. We can also scale them up (vertically) by “turning up the slider”.</p> <p>In the case of application code, it often turns out that scaling vertically won’t help. Adding a bigger machine might not help if our code is not designed for that, and it’ll be still running redundant database connections, calling other services directly etc. Sometimes the only solution is to scale horizontally. Well, how to do it without breaking down monolith?</p> <p><strong>The potential answer is setting several instances of the monolith.</strong> Sounds like a hack? It’s possible, but <a href="https://engineering.fb.com/2020/08/24/production-engineering/scaling-services-with-shard-manager/">Facebook does that</a>, and it works out pretty well for them.</p> <p>How to prepare for it?</p> <ol> <li><strong>Put API Gateway over our services.</strong> It will create a unified entry point for frontend calls. In the beginning, it will be a 1:1 version of our monolith API. Thanks to that, we will also prepare to cut it into smaller pieces in the future. Most of the cloud providers have it “out of the box”. (<a href="https://docs.microsoft.com/en-us/azure////api-management/">Azure</a>, <a href="https://aws.amazon.com/api-gateway/">AWS</a>). We can also use on-premise solutions like <a href="https://konghq.com/kong">Kong</a>. Thanks to this, the front end won’t be harmed by the effects of our next steps. They will be (relatively) transparent.</li> <li><strong>Configure the Load Balancer and/or reverse-proxy.</strong> It will be responsible for the even distribution of traffic between the clones of our monolith. Cloud providers also have built-in solutions here, but we can start with simpler ones like <a href="https://www.nginx.com/">NGINX</a>. It isn’t a difficult task, but the proper configuration of timeout handling, web sockets, etc., can be tricky. It is worth taking a moment to check the edge cases.</li> <li><strong>Set up several instances of our monolith</strong>. We can, for example, do it with Docker containers, Kubernetes, or simply Virtual Machines. It is also worth making sure that our services are stateless. If we use session data (e.g. <a href="https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-6.0#session-state">ASP.NET Session State</a>, we will have to set up a shared session - e.g. by the database, because each instance will have a different one.</li> <li><strong>Distribute reads evenly between instances.</strong> - Usually, the most traffic comes from the read requests. We must speed them up at the beginning. Long-lasting writes are more acceptable and less harmful than long reads.</li> <li><strong>Send all writes to one instance</strong>. Writes are much more prone to changes related to a distributed environment. It is safer to start with having one instance that handle writes. Then we can gradually try to spread the writes as well, but first, let’s begin with caution.</li> <li><strong>Try sharding instances per customer.</strong> We can set up load balancer rules to drive traffic based on the customer id sent in the request. That’s the easiest way to split the traffic, reduce the impact of our changes, and isolate load between different customers. Thanks to that, our biggest customer may not be impacting our other, smaller ones.</li> <li><strong>Consider using feature flags.</strong> We can add feature flags (so more sophisticated ifs) that will disable or enable some of the features in the specific instances. We can use it to disable some endpoints or features to shard monolith instances to handle requests only to particular modules. That can be a decent first step to break down our monolith into microservices. Check <a href="https://launchdarkly.com/">Launch Darkly</a> or your framework capabilities (e.g. <a href="https://docs.microsoft.com/en-us/azure/azure-app-configuration/use-feature-flags-dotnet-core?tabs=core5x">ASP.NET Feature management</a>).</li> </ol> <p>Is it enough? Can we call it a day? Of course not. It’s just the first step that can buy us some time. It will be probably more expensive than one instance. If it is successful, it will also give us more trust from the business. Then they may more likely allow us to spend time on refactoring. It will also be a stepping stone that will allow us to cut monolith into smaller chunks gradually.</p> <p>Additionally, it won’t be redundant. We will have to do those steps to go into microservices or independently hosted modules. How to do it? Read more in <a href="https://event-driven.io/en/how_to_cut_microservices/">How (not) to cut microservices</a>.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[I tested it on production and I'm not ashamed of it]]>https://event-driven.io/en/i_tested_on_production/https://event-driven.io/en/i_tested_on_production/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3d7470b2f734d8e05c87968599d7c77d/d2429/2022-02-02-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACnklEQVQozwXB608SAQAA8PvY+uDmcjY1QRDuUHncHXFPuPNQRJD3I3kdL5FHPlF0+Yjmls6gmqkf2mLDmm2SM8F0SjYdm5bWP9XvB/Tr2IDPF+Ej4RAf9Pk9TrfX7fZ7vUGvJxmNGDnWNmxM8HzY7wt7HCGHOeJ2uEdMDrOJITAgFo6USqVvh4d39/fXjcaXcnm/XP51dXV2dla/vPy6V97d2bm5/X1crZ6fX+xsb/+5u7v/++/H6SkfDAAEqh5gWKOeG/M/8zutHoOet1n8DrtlUO+zjawvzk957LNebzYUSAddcZfZTGMMhuq1pFwqAnolEgQCh0m1k6MMuJrplVgIjRZWoBKBWSNncQQVtolamhm5LEwhfr1a1dEEdjT3CFuxPhHA4U+HKJzDCTVM0ChqQHqcDDHSr6XgPo2000xhPMvaVH0mVOnA1YQCgyUQCglhqBMStAJ2jtQokU6hQipFSaXCoAKdOnyA1rAaBIfESok4zPXHWCJMY5wSE3XpxAJa0A6DT9pbmh4AZpYmYUQqgWUgyqBKIww6WULHUkGWDhDoEE1YCdQsF42x5LzJlo2lc5kZBreC3WJp12PAxDIqUCYUyKTdckoOsVDHCAm7rLYYw4Qo9YtkdAhRDItbbYgsyeimXLaNpbli/iUqA9sePQQmk+OTmQmej8Vjiezz1Mx4NDeRWplfyGdnFzPju1vvlrMzS9OTuZnJuVQ8v5Bbf7X68cPW2srSqN0CrK0sfyqV9vbKxcKbg4PKRb2+v79f3Ny8ub19XyzUqrXrRuO4Wjs6+n5R/1mpVN4WCtVarXZy8nmvDIzaLRytVSOkvAfpJzC/0z6dSugZIuq1J32eTJRPj8WT8dio1egaJF0GJpeOv15Z3MivzqUS/wEdHOL84KcfdgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/3d7470b2f734d8e05c87968599d7c77d/a331c/2022-02-02-cover.png" srcset="/static/3d7470b2f734d8e05c87968599d7c77d/36ca5/2022-02-02-cover.png 200w, /static/3d7470b2f734d8e05c87968599d7c77d/a3397/2022-02-02-cover.png 400w, /static/3d7470b2f734d8e05c87968599d7c77d/a331c/2022-02-02-cover.png 800w, /static/3d7470b2f734d8e05c87968599d7c77d/d2429/2022-02-02-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Testing on production usually appears in ridiculous stories.</strong> They may be only annoyances like getting questions from customers why they see the entry called <em>“Your momma wears Prada”</em>. The sad truth is that our users learned to live with mediocrity. The worst is if customers data get broken because of our clumsiness. <a href="https://about.gitlab.com/blog/2017/02/01/gitlab-dot-com-database-incident/">Even huge companies have a history of such accidents</a>.</p> <p>The more experience we have, the more we know that life is not black and white. We’re starting to embrace different shades of grey. We see that being SOLID police is not always the best idea. Sometimes you have to be pragmatic double-cautious to limit the number of side effects in case of failure. That sometimes means going rogue on the _“best practices”. When we see the idea to go by the books, we often start to say, <em>“that’s cool and smart, BUT…”</em>.</p> <p><strong>The whole movement related to CI, CD, and TDD aims to minimise the risk of failure.</strong> I support these approaches, but looking from the side, I see that often (especially tests) are used quite dogmatically. Some people even think that they could entirely prevent bugs.</p> <p><strong>Let’s take the Code Coverage example. Does having 100% test code coverage mean that all scenarios are covered?</strong> Nope, it means that we mechanically covered all ifs, thrown exceptions, etc. This metric verifies whether the tests check what our code is doing. They don’t necessarily ensure if it’s doing what it should. If the author didn’t consider some scenario, they wouldn’t catch that. Not every case ends up as tragically as <a href="https://www.nbcnews.com/tech/tech-news/self-driving-uber-car-hit-killed-woman-did-not-recognize-n1079281">Uber’s self-steering car killing pedestrians</a>. I’m sure that Uber had tests, but if developers did not come up with the fact that someone could be jaywalking…</p> <p>Often our situations are more trivial. We don’t know how the interface implementation or external library method works, and we write code and tests for our idea of ​​it, not its actual behaviour. Or the classic example. Some people want to cut the time the tests run using in-memory mocks instead of running them against the real database. It may turn out that the unit tests were all green, but the feature does not work in running it through UI.</p> <p><strong>At Marten, we encourage running all Marten tests against the database. Running Docker isn’t a massive effort these days.</strong> The Postgres image is so lightweight that these tests should be quick enough. Preference to run unit tests over integration tests became obsolete. These assumptions were made when setting up the local test environment was a hassle. Plus, many services, like databases, added significant overhead—Times change. We are in the 21st century. Let’s start revising old views. Read more in <a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a>.</p> <p>Clouds, distributed systems, Kubernetes and other toys add a degree of complexity. We can indeed run a Kubernetes cluster locally on our laptop. We can even run a cloud emulator, but this is still not the same environment as production. It’s like buying shoes. I wear (in general) 43 in European size. Still, I better try that with the specific model, as size differs significantly between producers.</p> <p><strong>Until we run it on the target environment, we won’t know how our system behaves.</strong> There is a lot of differences to consider:</p> <ul> <li>hosting (bare metal vs VM),</li> <li>disk type,</li> <li>network topology,</li> <li>OS or runtime versions,</li> <li>load balancers and reverse proxies,</li> <li>API gateways,</li> <li>etc.</li> </ul> <p>We’ll also get a different type of load and traffic. Do we test spikes locally in traffic (e.g. for Black Friday)? We cannot fully predict our end-users behaviour until we see it.</p> <p><strong>The classic recipe was “pre-prod”.</strong> We try to mimic the production environment, deploy there and run User Acceptance Tests. Sounds reasonable? Kinda, but it’s not. Somehow it always turned out that this environment was slightly different from production. And those slight differences are usually the weakest points. Usually, at some point, we realise that it does not help us at all and just adds maintenance overhead.</p> <p><strong>It is important to be reactive, taking action when something breaks.</strong> However, it’s even more important to be proactive. To constantly verify what’s going on. By that, I mean defining the proper tracing, metrics and alerts. That’s essential to see the flaws. But this is also often not enough.</p> <p><strong>In my past project, we used synthetic tests.</strong> They simulate the operation of real system users. Think of them as the end to end tests, but run on prod. We had them trying to stress production with a schedule (e.g. every 5 minutes). Thanks to that, it was possible to detect system anomalies by combining the test results with metrics. For example, after deploying a new feature, we caught that we had performance regression in a set scenario. Thanks to this, it was possible to see issues before the client reported them. Of course, the preparation of such a set of tests requires thought, time and configuration, but it gives us quick and reliable information about our system. As an example of that process, check <a href="https://raygun.com/blog/synthetic-testing/">article from Raygun</a>.</p> <p><strong>Netflix has also popularised another way of testing systems. They called it <a href="https://github.com/Netflix/chaosmonkey">Chaos Monkey</a>.</strong> A monkey that randomly disables our services in production and allows us to check how resistant our system is to errors. That information can tell us whether we have a distributed monolith or not. Cloud providers <a href="https://aws.amazon.com/blogs/devops/chaos-engineering-on-amazon-eks-using-aws-fault-injection-simulator/">started even to offer that as a service</a>. Still, you don’t need a fancy infrastructure for that. You could <a href="https://www.gremlin.com/chaos-monkey/chaos-monkey-alternatives/docker/">try it even with Docker</a>.</p> <p>If I didn’t convince you, and you’re into podcasts, check <a href="https://corecursive.com/019-test-in-production-with-charity-majors/">Corecursive podcast episode with Charity Majors on that topic</a> or read <a href="https://increment.com/testing/i-test-in-production/">her article</a>. She’s one of the major advocates of the “test on prod” movement.</p> <p>What’s your experience with that? Are you in the Testing-on-prod-is-a-blasphemy team?</p> <p>Read also:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/behaviour_driven_design_is_not_about_tests/">Behaviour-Driven Design is more than tests</a></li> <li><a href="/en/testing_event_driven_projections/">How to test event-driven projections</a></li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[A simple trick for idempotency handling in the Elastic Search read model]]>https://event-driven.io/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/https://event-driven.io/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ae76e1522a95d90262a065e1e303cb1d/d2429/2022-01-26-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACi0lEQVQozxXIbVPSAAAA4KUgDNiAMWAMByRsAwK2sTHe5zZAXgRBMDURfEuj8tTUxDOuu9TLuuu8s+y6yy+dd9Xn7vqBXc/HB4hmw/aHOKtEC48zSjs5u6pWV5RcXZArXF4Kp6WYqHJSIzXXK8hzKZz2htNsbFpkCyJfzgJxNeaccvO58HSFb60rT3arC4Nys1f4dXfw/qRZL7F5hU1XBLWT5xTWEyHDWY5TU4yS4IpJgFUZXua+fx70F6fbi/LgrN09qPYHs39/nh5uKRkx4JpEOSnW7leG+63OfC4uJyJSIpKPh7McwKqxUiP58bInJoIMT2/s1TePm731Uj4VDJKYFYWtVqMv5Nl52f59s90scYrCHT1rCBLnZWmg2lf3n9d6K1Kcp3wkMTzv771bGg2Xbs77C3OiwahDUYhJP3q6WevURdxtoyj3cj1JTLmIKAl0XzWkGeH6arXTymYV/tuPk8PL7uuzpfvbF6vtDGiY8JK4MJPYGjSv33YddnNNiRVyEWTSTkSmgM1hm+Ko4XGL5f0pmfvwdXfruLmwXf3yaWenJ2s1Y0QAF4qJ2nJ5dNixWMCNXnm+lnSQhDdGAQGOnKS8F9dH+bLoiQbIOO3y42YMGV3sno7WMQJ1BTwuyutn6KvbN8psNpphBDnhY2gfQwGwA7H5nGt7a+mZDOrBzBiqNxse6MYy5dz9nzuSpbAAYTAbQQQS1BQviyACQXaLFUf1EAhAKOwOef08jdMeIwI5PU4rhphQ2EUTxfkiQjiMVpMO1EE2eFw7rtFqbG7UZIP1sMFitwB6kx6jCDdFuAJu1IeFUhEyHjTZYMRjx8j/Y8EQrV4LwgatTqOd0IAmEITACYPOG/L9A2PFkEEbxHCwAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ae76e1522a95d90262a065e1e303cb1d/a331c/2022-01-26-cover.png" srcset="/static/ae76e1522a95d90262a065e1e303cb1d/36ca5/2022-01-26-cover.png 200w, /static/ae76e1522a95d90262a065e1e303cb1d/a3397/2022-01-26-cover.png 400w, /static/ae76e1522a95d90262a065e1e303cb1d/a331c/2022-01-26-cover.png 800w, /static/ae76e1522a95d90262a065e1e303cb1d/d2429/2022-01-26-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Idempotency is a word worth watching out for. It’s easy to miss a few letters and bang, and we have a problem. It is also a general problem in programming (or, as some people prefer, <em>“a challenge”</em>). We should be aware of it. In short, performing the same operation several times should not cause side effects, e.g. the duplicates resulting from running addition more than once or charging fees multiple times for a single transaction.</p> <p>The problem is general because even in the REST API, we assume that all operations except POST should be idempotent by definition. In distributed systems and event-based architectures, this problem is even more emerging. We’re using retries policies or <a href="https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">outbox pattern</a> to increase the fault tolerance (e.g. unavailability of a database or service). Queues like Kafka and Rabbit use these approaches internally to ensure message delivery. To deliver it at least once, they have to repeat the processing in case of failure. That may cause delivering the given message several times. If we don’t want to have bugs related to it, we have to deal with it somehow, but how?</p> <p>Suppose we have two services. One is the source of the truth and publishes the events after the business process is completed. The second one subscribes to these events and updates the Elastic Search read model. Let’s be optimistic and assume that we guarantee that the events will be delivered in the order in which they were published. We do not guarantee that it will only be delivered exactly once.</p> <p>If we want to be sure that a given event will not be processed many times, we must distinguish it somehow. Support for optimistic concurrency can help. We could use it to make sure that we’re making changes based on the current state. If we’re also using an auto-incremented number that’s updated after each change, then we can also use it to handle idempotency (read more in my article: <a href="https://event-driven.io/pl/how_to_use_etag_header_for_optimistic_concurrency/">How to use ETag header for optimistic concurrency</a>). We can send the version number together with a published event. We get a unique change indication by combining the record ID and its version.</p> <p>ElasticSearch provides several index versioning options. The one that interests us the most is called <em>external</em>. It assumes that ElasticSearch is not the source of truth and that the version number is ascending number. It does not assume whether the values ​​can have gaps or not. The only thing that verifies is that the version sent during the update is:</p> <ul> <li>smaller,</li> <li>equal or greater than the current document version.</li> </ul> <p>An update is only possible for the second option. Thanks to that, it won’t allow us to apply the event more than once. If we try to process the event twice, the second attempt will fail because the version we provide will be equal. In this case, we can just ignore this error and proceed with the next event.</p> <p>In C#, this code would look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">StreamEvent</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> id <span class="token operator">=</span> <span class="token function">getId</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> indexName <span class="token operator">=</span> IndexNameMapper<span class="token punctuation">.</span><span class="token function">ToIndexName</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> entity <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">await</span> elasticClient<span class="token punctuation">.</span><span class="token function">GetAsync</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> i <span class="token operator">=></span> i<span class="token punctuation">.</span><span class="token function">Index</span><span class="token punctuation">(</span>indexName<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">?.</span>Source <span class="token operator">??</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TView</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> entity<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> elasticClient<span class="token punctuation">.</span><span class="token function">IndexAsync</span><span class="token punctuation">(</span> entity<span class="token punctuation">,</span> i <span class="token operator">=></span> i<span class="token punctuation">.</span><span class="token function">Index</span><span class="token punctuation">(</span>indexName<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Id</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">VersionType</span><span class="token punctuation">(</span>VersionType<span class="token punctuation">.</span>External<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Version</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">long</span><span class="token punctuation">)</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>Metadata<span class="token punctuation">.</span>StreamRevision<span class="token punctuation">)</span><span class="token punctuation">,</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It is a simple trick, but it will help avoid the headache around duplicates. We’re getting a slight performance penalty compared to the regular versioning, but we can often neglect that as increased reliability is much more critical.</p> <p>Read more:</p> <ul> <li><a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning">https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning</a></li> <li><a href="https://www.elastic.co/blog/elasticsearch-versioning-supporthttps://www.elastic.co/blog/elasticsearch-versioning-support">https://www.elastic.co/blog/elasticsearch-versioning-supporthttps://www.elastic.co/blog/elasticsearch-versioning-support</a></li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[Using strongly-typed identifiers with Marten]]>https://event-driven.io/en/using_strongly_typed_ids_with_marten/https://event-driven.io/en/using_strongly_typed_ids_with_marten/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8e481f97830a1f4ae0a8f155cf1a4aeb/d2429/2022-01-19-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACi0lEQVQoz02QWU/aAACA+z/2K5b4H3zYli1ecQ51HjDkLFSuWij3VQTkKDLAikId0BGRQ5SrhRQGymIyp8m2bA8UzZbsPyzGlyXf65cv+QB79y9G//JkympvzOB0amE5ardCeiO2E6xVsukkXm73wuzY0x47mLG5xaGNkaHBPQIYI/uhdM5ohVelkBuPhhNBuVYu1iIGm+PrVa9xRoUwTZxMxwZ/3PTIQXP/+4DVCm0iOrnJJdGhQmjL5Asrzfbl9UXHti9HxoIBNBrHyqUse3mZuPjtYu7sNGdqjR59IEmRcq1MCOmUBus7MZ8vkSFmy4oYNFptbKtwfppnO+exiJPc9zGDixDz09W+t9Gc8aHPAU487sQ9bxWgEnVhO14IFq+p1CtiCEbRm89Ms0ol9/xOl7ZQ+lgukMmjVLx/52TubDRnanKAyoJpPH6+zswTQt7I+2WTa3ptY3p+DjZZht16vphTI7L88YdPXWY4aDdrJeIgttcfYZ2Hf8BrGbggUApArVzKlyrlctiiUEkmZ+fc294sGXU7dFQxX63kdwO2YpEqFjK+bfTq9iY1HHvZe2BBwOeJlNE4vrEFrhscMtgsRazLIlCj1QyYSiqJ95mzDBlHbXCtVskcESHc12Pr5WopSt8Cz0VrMxKVM4TPaOy8N7OLS3PvNreevZwOJ4jv1/0rtj7sMwq92ut3sJ1mp3V6ckIF/KbjApU4IICJVfW8QPFiSXx4GOfD4JNZqQyxvJpfksok3bNcuZDRwxI8EfVHAsSuv1M7qZbzBuNmnW7ksmmAJwSlBtvkFC9IEE830ImpdSViWRRBWDDy5YL+8e1aEwzCauH1Zc8T8un1EJUlEaPuMJVMHhD/AGHKgfNh0SK4AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/8e481f97830a1f4ae0a8f155cf1a4aeb/a331c/2022-01-19-cover.png" srcset="/static/8e481f97830a1f4ae0a8f155cf1a4aeb/36ca5/2022-01-19-cover.png 200w, /static/8e481f97830a1f4ae0a8f155cf1a4aeb/a3397/2022-01-19-cover.png 400w, /static/8e481f97830a1f4ae0a8f155cf1a4aeb/a331c/2022-01-19-cover.png 800w, /static/8e481f97830a1f4ae0a8f155cf1a4aeb/d2429/2022-01-19-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Let’s say that you have the following class definition:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Reservation</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> AggregateId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> SeatId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> CustomerId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">Reservation</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> aggregateId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> seatId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> customerId <span class="token punctuation">)</span> <span class="token punctuation">{</span> AggregateId <span class="token operator">=</span> aggregateId<span class="token punctuation">;</span> SeatId <span class="token operator">=</span> seatId<span class="token punctuation">;</span> CustomerId <span class="token operator">=</span> customerId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Now you can create instance using:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> reservationId <span class="token operator">=</span> <span class="token string">"RES/01"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> seatId <span class="token operator">=</span> <span class="token string">"SEAT/22"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> customerId <span class="token operator">=</span> <span class="token string">"CUS/291"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reservation <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationId</span> <span class="token punctuation">(</span> reservationId<span class="token punctuation">,</span> seatId<span class="token punctuation">,</span> customerId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>So far, so good. What if we accidentally mixed the order of the parameters to, e.g.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> reservation <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationId</span> <span class="token punctuation">(</span> seatId<span class="token punctuation">,</span> customerId<span class="token punctuation">,</span> reservationId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then either we have good unit tests or just created a bug. A nasty one. The compiler won’t catch the difference because all types are the same. That would be different if we had different types for each id type, right? For instance:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Reservation</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">ReservationId</span> AggregateId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">SeatId</span> SeatId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">CustomerId</span> CustomerId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token function">Reservation</span><span class="token punctuation">(</span> <span class="token class-name">ReservationId</span> aggregateId<span class="token punctuation">,</span> <span class="token class-name">SeatId</span> seatId<span class="token punctuation">,</span> <span class="token class-name">CustomerId</span> customerId <span class="token punctuation">)</span> <span class="token punctuation">{</span> AggregateId <span class="token operator">=</span> aggregateId<span class="token punctuation">;</span> SeatId <span class="token operator">=</span> seatId<span class="token punctuation">;</span> CustomerId <span class="token operator">=</span> customerId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then we could use it like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> reservationId <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationId</span> <span class="token punctuation">(</span><span class="token string">"RES/01"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> seatId <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SeatId</span> <span class="token punctuation">(</span><span class="token string">"SEAT/22"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> customerId <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CustomerId</span> <span class="token punctuation">(</span><span class="token string">"CUS/291"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reservation <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationId</span> <span class="token punctuation">(</span> reservationId<span class="token punctuation">,</span> seatId<span class="token punctuation">,</span> customerId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then compiler would help us to provide wrong values accidentally by switching the order of parameters, etc. This approach is especially popular in the Domain-Driven Design community with a connection to the Aggregate pattern.</p> <p>Shortly Aggregate is a “business transaction”. It guarantees that all its data will be stored atomically, respecting the business rules and invariants. It’s also grouping other tactical patterns, like exposing publicly only what’s needed and setting data only through public methods. Yup, strongly typed values also matches that.</p> <p>We could define a base class for strongly typed values, as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEquatable<span class="token punctuation">&lt;</span>StronglyTypedValue<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">></span></span></span> <span class="token keyword">where</span> <span class="token class-name">T</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IComparable<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> Value <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">StronglyTypedValue</span><span class="token punctuation">(</span><span class="token class-name">T</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Value <span class="token operator">=</span> <span class="token keyword">value</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">Equals</span><span class="token punctuation">(</span><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">?</span></span> other<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">ReferenceEquals</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> other<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">ReferenceEquals</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> other<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token keyword">return</span> EqualityComparer<span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">.</span>Default<span class="token punctuation">.</span><span class="token function">Equals</span><span class="token punctuation">(</span>Value<span class="token punctuation">,</span> other<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">Equals</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">?</span></span> obj<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">ReferenceEquals</span><span class="token punctuation">(</span><span class="token keyword">null</span><span class="token punctuation">,</span> obj<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token function">ReferenceEquals</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> obj<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>obj<span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">!=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Equals</span><span class="token punctuation">(</span><span class="token punctuation">(</span>StronglyTypedValue<span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">)</span>obj<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> <span class="token function">GetHashCode</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> EqualityComparer<span class="token operator">&lt;</span>T<span class="token operator">></span><span class="token punctuation">.</span>Default<span class="token punctuation">.</span><span class="token function">GetHashCode</span><span class="token punctuation">(</span>Value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">bool</span> <span class="token keyword">operator</span> <span class="token operator">==</span><span class="token punctuation">(</span><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">?</span></span> left<span class="token punctuation">,</span> <span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">?</span></span> right<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">Equals</span><span class="token punctuation">(</span>left<span class="token punctuation">,</span> right<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">bool</span> <span class="token keyword">operator</span> <span class="token operator">!=</span><span class="token punctuation">(</span><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">?</span></span> left<span class="token punctuation">,</span> <span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">?</span></span> right<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token operator">!</span><span class="token function">Equals</span><span class="token punctuation">(</span>left<span class="token punctuation">,</span> right<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>It’s a simple wrapper for the primitive type, with additional overloads for checking equality. We can define base classes for our needs as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ReservationId</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">ReservationId</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CustomerId</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">CustomerId</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">SeatId</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">SeatId</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>This gives us the option to add additional validation. We could e.g. expand the check for the proper format, getting even more trust in our objects:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ReservationNumber</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">ReservationNumber</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token keyword">value</span><span class="token punctuation">.</span><span class="token function">StartsWith</span><span class="token punctuation">(</span><span class="token string">"RES/"</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token keyword">value</span><span class="token punctuation">.</span>Length <span class="token operator">&lt;=</span> <span class="token number">4</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>That’s a useful pattern, and recently I’ve been asked multiple times how to use strongly typed ids with <a href="https://martendb.io/">Marten</a>. It appears that’s non-trivial. Marten requires a few things to work correctly:</p> <ul> <li>Id has to be <em>string</em> or <em>Guid</em> (or <em>int</em>, <em>long</em> if you’re just using document part),</li> <li>public Id with getter and setter,</li> <li>default constructor (not necessarily public).</li> </ul> <p>Let’s start with defining the base class for our attribute:</p> <div class="gatsby-highlight has-highlighted-lines" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Aggregate<span class="token punctuation">&lt;</span>TKey<span class="token punctuation">,</span> T<span class="token punctuation">></span></span> <span class="gatsby-highlight-code-line"> <span class="token comment">// 1</span></span> <span class="token keyword">where</span> <span class="token class-name">TKey</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">StronglyTypedValue<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IComparable<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">TKey</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="gatsby-highlight-code-line"></span> <span class="token comment">// 2</span> <span class="token punctuation">[</span>Identity<span class="token punctuation">]</span> <span class="gatsby-highlight-code-line"> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> AggregateId</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token operator">=></span> Id<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token comment">// 3</span> <span class="token keyword">set</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Version <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// 4</span> <span class="token punctuation">[</span>JsonIgnore<span class="token punctuation">]</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Queue<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> uncommittedEvents <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token function">DequeueUncommittedEvents</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> dequeuedEvents <span class="token operator">=</span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">ToArray</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">Clear</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> dequeuedEvents<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Enqueue</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> uncommittedEvents<span class="token punctuation">.</span><span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>I used a few tricks here:</p> <ol> <li>Generic type params (especially <em>where TKey: StronglyTypedValue<T></em>) will allow me to know that my Id is a composite key and access internal <em>Value</em>.</li> <li>Thanks to <a href="https://martendb.io/documents/identity.html#document-identity">Identity attribute</a>, I could change the default name for the Marten identifier. It still has to be public, but at least it’s hidden. If you prefer, you could use <em>DoNotTouchItPlease</em> instead.</li> <li>We still need to have a public setter, but at least it’s not doing any redundant stuff. We’ll be using the composite key for (de)serialisation. Ignoring this setter also has the benefit that it cuts some magic. If we had to define it, we’d also need a factory method to generate composite ids or overload in each class. This comes from the fact that C# only allows defining <em>new ()</em> type constraint. Our strongly typed values are immutable and don’t have a default constructor.</li> <li>You can safely ignore <em>uncommitedEvents</em>, <em>DequeueUncommittedEvents</em>, and <em>Enqueue</em> if you’re not doing Event-Driven Architecture/Event Sourcing. This is a pattern to cache an event generated by the business logic and then append it to the event store and/or publish it to the queue.</li> </ol> <p>The result Aggregate (with Event Sourcing flavour) would look like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Reservation</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate<span class="token punctuation">&lt;</span>ReservationId<span class="token punctuation">,</span> Guid<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">CustomerId</span> CustomerId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">SeatId</span> SeatId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ReservationNumber</span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">ReservationStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Reservation</span> <span class="token function">CreateTentative</span><span class="token punctuation">(</span> <span class="token class-name">SeatId</span> seatId<span class="token punctuation">,</span> <span class="token class-name">CustomerId</span> customerId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Reservation</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationId</span><span class="token punctuation">(</span>Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> seatId<span class="token punctuation">,</span> customerId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationNumber</span><span class="token punctuation">(</span>Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">Reservation</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment">// to make Marten happy</span> <span class="token keyword">private</span> <span class="token function">Reservation</span><span class="token punctuation">(</span> <span class="token class-name">ReservationId</span> id<span class="token punctuation">,</span> <span class="token class-name">SeatId</span> seatId<span class="token punctuation">,</span> <span class="token class-name">CustomerId</span> customerId<span class="token punctuation">,</span> <span class="token class-name">ReservationNumber</span> reservationNumber <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TentativeReservationCreated</span><span class="token punctuation">(</span> id<span class="token punctuation">,</span> seatId<span class="token punctuation">,</span> customerId<span class="token punctuation">,</span> reservationNumber <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">ChangeSeat</span><span class="token punctuation">(</span><span class="token class-name">SeatId</span> newSeatId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ReservationStatus<span class="token punctuation">.</span>Tentative<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Changing seat for the reservation in '</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">' status is not allowed."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationSeatChanged</span><span class="token punctuation">(</span>Id<span class="token punctuation">,</span> newSeatId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Confirm</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ReservationStatus<span class="token punctuation">.</span>Tentative<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Only tentative reservation can be confirmed (current status: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationConfirmed</span><span class="token punctuation">(</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Cancel</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Status <span class="token operator">!=</span> ReservationStatus<span class="token punctuation">.</span>Tentative<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Only tentative reservation can be cancelled (current status: </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">Status</span><span class="token punctuation">}</span></span><span class="token string">)."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ReservationCancelled</span><span class="token punctuation">(</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Enqueue</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">TentativeReservationCreated</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ReservationId<span class="token punctuation">;</span> SeatId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>SeatId<span class="token punctuation">;</span> CustomerId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CustomerId<span class="token punctuation">;</span> Number <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Number<span class="token punctuation">;</span> Status <span class="token operator">=</span> ReservationStatus<span class="token punctuation">.</span>Tentative<span class="token punctuation">;</span> Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ReservationSeatChanged</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> SeatId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>SeatId<span class="token punctuation">;</span> Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ReservationConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ReservationStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">;</span> Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ReservationCancelled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ReservationStatus<span class="token punctuation">.</span>Cancelled<span class="token punctuation">;</span> Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>That’s almost all. We just need to tell Marten that we’d like to use non-public setters and non-public non-default constructors. We do it by <a href="https://martendb.io/configuration/json.html#non-public-members-storage">setting the serialisation options</a>:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">options<span class="token punctuation">.</span><span class="token function">UseDefaultSerialization</span><span class="token punctuation">(</span><span class="token named-parameter punctuation">nonPublicMembersStorage</span><span class="token punctuation">:</span> NonPublicMembersStorage<span class="token punctuation">.</span>All<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p><em>Et voilà!</em> This should be enough to make both event store and document part working (so <a href="https://martendb.io/events/projections/live-aggregates.html">stream aggregation</a> and <a href="https://martendb.io/events/projections/inline.html">inline projections</a>). The last caveat is that if you’re querying by the composite value, you need to explicitly use it, as then Marten won’t generate a proper query, and Postgres won’t understand the custom type. See:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> reservation <span class="token operator">=</span> Session<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Query</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Reservation<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>r <span class="token operator">=></span> r<span class="token punctuation">.</span>ReservationNumber<span class="token punctuation">.</span>Value <span class="token operator">==</span> <span class="token string">"RES/293"</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>I’ll let you decide if that’s the approach you’d like to go. It’s mostly a matter of your preferences. A few tricks/hacks are needed, but an improved type check at compilation time can give you enough benefits to accept this trade-off.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. see the full sample in my repo: <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/94">https://github.com/oskardudycz/EventSourcing.NetCore/pull/94</a>.</p><![CDATA[Should a programmer's creativity be shown in code formatting?]]>https://event-driven.io/en/should_programmers_productivity_be_shown_in_code_formatting/https://event-driven.io/en/should_programmers_productivity_be_shown_in_code_formatting/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/961adcd01246ac7632dc0738968898db/d2429/2022-01-12-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACR0lEQVQoz1WS709SURzG+TN627vetl70wtUk28wVq42fVjARIhlwkV/xq6k4IhAVVBDQEaEOMGrFSmuztTWXLddGm5XlNHBrLOFeLpfhha73ntsupOXz5mznu895nuecw6IPRRAE1hQMwyiKVioVDMOq1WqhUCgWiyha3d/HERiu1+tHCKu1AABQFCV+EziOEwTR2gEAtKYUBUrFHwAQgKZLMIzj+DGYJEkEQej/RDX1M5fHEMRoMs/P+XezXz+l36G/ELiMHIMbjUa5XGZOoSiSJCmSomm6juP37UMuh1PV17f5LZ+yRiwd0Fp4qVarHhyQDNzKVqvVMKxC/cvJwJ9XV60jXovbHboXjAcspn5N1BRx6ILLbz8yvY7gUglOp5ez2fWpxXQ09mJvO7+1szWcfMJWQR6d1XnF6OT0LtwNTELmExwN5H3IGADAAk2Twvcvdr7UNpMR2oJtp87ypKLQrHU6/sr9IGq3Sy1ivUdgcHEN/d2DJ8/rdCOHMEUx6TfWX+uFl86pvByOUnKmS6E0hGNOgW6sU6kNhfhmhUwEDV6QGW/ZxqUqp8OTYB6Aplmteu+zm52eifZutaBdruPdtgoVfmhYI9Zze+QhFQ9S68fnl+KBObdQzBYZRycyjPNR5+Jeoc8rYovkdyQij6ttaOC0hivRdF29LLhxsaNnLPAou7GdTGau8a9z1DLHzOLf2M1rowGJf3jpGPCHvbOWnbWbuQ3t0wVfyuObnvT1Dk9FHq/kdpHEszda22giFY49X2l9rD+f4una2svSlAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/961adcd01246ac7632dc0738968898db/a331c/2022-01-12-cover.png" srcset="/static/961adcd01246ac7632dc0738968898db/36ca5/2022-01-12-cover.png 200w, /static/961adcd01246ac7632dc0738968898db/a3397/2022-01-12-cover.png 400w, /static/961adcd01246ac7632dc0738968898db/a331c/2022-01-12-cover.png 800w, /static/961adcd01246ac7632dc0738968898db/d2429/2022-01-12-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Each of us has at least a dose of creativity. I have always liked drawing. I wanted to make effective plays in football. Today, I play the guitar and write blog articles for you. I was not outstanding in any of these things, but I enjoyed every form of it. You don’t have to be great at something to be creative. This has pros and cons.</p> <p>Let’s say programming. I used to consider programming as an art. To this day, I remember a few of my tirades on this subject. Good class structure, SOLID, DRY principles, those things. Fortunately, I didn’t go to the extreme to join the tabs vs spaces war. People can argue whether you should indent two spaces or four for a long time. Or, for example, braces. Should they be in the same line or the next one? Do we even call them braces or brackets? C# devs can probably still argue if using the dynamic var variable or a explicitly typed variable is better. Each language has its own conventions. Every developer has an opinion on this and won’t hesitate to use it!</p> <p>Over the years, I have concluded that we’re not artists. We have to do our job so that the customer is satisfied. Does this take away some magic from us? Maybe a bit, but is it wrong? Do we expect creativity from a car mechanic or welder?</p> <p>It seems that a lot of confusion comes from a dissonance between how we picture ourselves and what we actually do. Our projects are usually neither very unique nor insanely ambitious. Hence, our medley in selecting technology, formatting, discussions or some kind of convention.</p> <p><a href="https://www.youtube.com/watch?v=hPfVIoB9C0c">I am a perfectionist</a>. Dad and Grandpa taught me that <em>“either do something right or not at all”.</em> At first glance, this looks like a good approach; it seems more like a curse at the second one. That is why I fully understand people getting mad by the unnecessary new line. Nothing irritates me more than laziness and sloppiness. When I do PR, unnecessary spaces, redundant else ifs and unnecessary typing immediately catch my eye.</p> <p>You could say I’m picky, and you’re probably right. Still, there is also the other side of the coin. There is no such thing as multiple conventions - there is either one or no one at all. If every programmer starts to implement their own conventions, it’s just patchwork, not the source code. Being sloppy on such simple subjects is kinda disrespectful to your colleagues.</p> <p>Apart from the aesthetic value, it hurts delivery time and thus money. How so? Different code styles in each file make the codebase challenging to digest. Most of us are visual learners. Looking at something, we unconsciously look for patterns with what we know. If the code is written uniformly, it becomes transparent to us. We don’t wonder and not get disturbed by why someone wrote it like that or the other way round. We just look at code able to focus on the business logic understanding.</p> <p>This is important when we write code and when we do code review. I think you can recall discussions from review and comments like <em>“why is this brace here?”, “Why don’t you give var here?”, “Why don’t you check null here?”</em>. Often, those disputes are the most heated ones. They have nothing to do with the essence of the changes checked. Both sides lose precious time and positive energy.</p> <p>Interestingly, not only us humans but also automations are better when dealing with patterns. The diff tools will show the differences better if the code is formatted according to the same rules.</p> <p>It used to be challenging to implement this. It was a pain to set up static code analysis tools. Usually, some sort of Sonar tool would fire once in a while and then try to make recommendations if there was time (yep, there never was!). Today, virtually every language and IDE support <em>.editorconfig</em> files to define source code rules. In addition, tools such as ESlint, Prettier in JavaScript and more, code analyzers in .NET, etc. etc. allow you to automate this process. We can now autoformat on each save, check the rules, etc.</p> <p>There is nothing worse in the long term for an organizational culture than rules that are made to be broken. If we introduce a rule or principle in a project, it must be verifiable. We cannot arrange something and then do not check whether we are actually doing as was agreed. We should make real arrangements. I dislike the <em>“developer has to know and remember this”</em> approach. We can be 100% sure that people will forget it regularly if we think so. If we have 10 people in the team, most probably statistically, someone will forget something every day. Therefore, the tedious processes must be automated.</p> <p>When I tried to use Linter and Prettier in one of my previous projects, I met a lot of resistance at first. Artists wanted to prove who’ll put newlines most beautifully. Yet, a patient drop has hollowed out the rock, repository after repository. At some point, even the most die-hard opponents started to add it to their repos themselves. Then a lot of code review fights and unnecessary discussions ended. It just works.</p> <p>Of course, linters aren’t just pretty code. Static code analysis also allows you to avoid errors and reduces the number of necessary tests. They catch issues that are easy to miss. IDE and plugins from JetBrains (e.g. ReSharper) have always been doing that.</p> <p>If you want to see how you can configure it, e.g. in TS, please visit my repo: <a href="https://github.com/oskardudycz/EventSourcing.NodeJS">https://github.com/oskardudycz/EventSourcing.NodeJS</a>. You can also look at the rules in the code in the EventStore - <a href="https://github.com/EventStore/EventStore/blob/master/src/.editorconfig">https://github.com/EventStore/EventStore/blob/master/src/.editorconfig</a>.</p> <p>I really recommend setting up some set of rules. First, compromise, then you can establish more controversial rules. I also recommend not to implement formatting changes and changes in business logic. Let’s do a separate PR with corrections. It is much easier for such a PR to be approved in a finite time when we know that we have not changed anything besides linter and formatting.</p> <p>As Kent Beck says, let’s <a href="https://www.youtube.com/watch?v=3gib0hKYjB0">make the change easy, then make the easy change</a>!</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[No, it can never happen!]]>https://event-driven.io/en/no_it_can_never_happen/https://event-driven.io/en/no_it_can_never_happen/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ccec3d8cf76c30a9b4972ce7ee481efa/d2429/2022-01-05-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABjUlEQVQoz31SPU8CQRDdGGtjYmKlsfEH2FqhhYWNsfAnWFn4A2y0trK0sVQrY4yJqEFOjYASI6DEL0CUOzg4jl3ubndnD+5YE22Urykn8+bNe28Q7ygQra/Pl9zLGSE1AAEAnHPGWOck6mypufPCzWQpjMqPy2bNOTk5VpRwPB63bet3UTcwY0J4NePtOThWVBC9R58XQ7lM4v09m//IaZpKKe3NzBxwZVUL4yhKHw1kzgazQZRJboMrbbsuhAAOfc6mlEsjtViJjcSPAw/7468Ho5nrubJBflX30cwAhGNb5cgwTgdSyk5oc/pya/4ptGqaqmHo1LHbBP8D/1AL/X6GRFAhuZGKnCq7S4W3Q1XNuw1PL+uWZfcxjILwsZHUYlOkeMQ9mbhayQdR5Xai9LCg3gXMalGIBv+TWVtUDIRXt4hpfOl6qY6zWnrdSMziCCLRwWoxKlzJGO2dM3MEcMYA42qr5Ukp3UYTq3vq05pFKgDuX+e6PAlwTggGoL7vNpvU90FKyaBh2U6bZd9GV0iEP4yMIQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ccec3d8cf76c30a9b4972ce7ee481efa/a331c/2022-01-05-cover.png" srcset="/static/ccec3d8cf76c30a9b4972ce7ee481efa/36ca5/2022-01-05-cover.png 200w, /static/ccec3d8cf76c30a9b4972ce7ee481efa/a3397/2022-01-05-cover.png 400w, /static/ccec3d8cf76c30a9b4972ce7ee481efa/a331c/2022-01-05-cover.png 800w, /static/ccec3d8cf76c30a9b4972ce7ee481efa/d2429/2022-01-05-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><em>“No, it can never happen!”</em>. Have you heard this sentence before? For example, a user with the same e-mail address may not register, or the shipment won’t be delivered.</p> <p>I want to tell you about a specific requirement and a situation that had no right to occur. I was working once on the financial module of the system in the hospitality industry. We had the requirement that guests couldn’t check out if they didn’t pay for the stay.</p> <p>The process of checking out a guest is quite complex. We need to calculate the balance and make sure that it’s zeroed, so if all charges were paid. You should notify the person who will clean the room and ensure that the guest is not there yet. If everything is correct, you can mark the room as available so that another person can check-in.</p> <p>Technically, the process was started in the financial module. We blocked the guest’s account during the checkout so that its status would not change during the checkout. When payments didn’t match charges, the process finished with an error. Someone from the front desk had to ask the guest for a surcharge. We informed the guest accommodation module when everything went fine. This module had to do stay summaries, update availability, etc. It sent back the event with a confirmation that all processing succeeded. Then, the financial module could close the checkout process.</p> <p>According to the requirements, if the financial module stated that the guest could be checked out, no other module was allowed to deny that fact. All it had to do was accept it and run its internal business logic.</p> <p>So much for the theory. In practice, it was different. There were many reasons it could fail—from the most prosaic programming errors to changing or sometimes contrary requirements.</p> <p>An extreme case was as follows:</p> <ol> <li>The financial module started the process of checking out the guest. It blocked the guest’s financial account and sent the event to the guest accommodation module.</li> <li>Some error occurred in the guest accommodation module. Because of that, no event was sent back to the financial module.</li> </ol> <p>Because of that, the check out process was stuck. Thus:</p> <ol> <li>No operation on the blocked guest’s financial account could be performed.</li> <li>There was a discrepancy. The financial module stated that checkout had started, but the guest accommodation module didn’t acknowledge that fully.</li> </ol> <p>In the hospitality industry, there is a <a href="https://www.hotelogix.com/blog/2021/05/19/night-audit-procedure-in-hotels-with-cloud-hotel-pms/">night audit process</a>. Its major functions:</p> <ul> <li>Ensures rollover from one business day to the next day</li> <li>Reconciles all front office cash counters/accounts</li> <li>Verifies posted entries to guest/non-guest accounts</li> <li>Resolves room status and rate discrepancies</li> <li>Generates several reports called night audit reports.</li> </ul> <p>If one of the business rules is not fulfilled, you cannot finish the process until the discrepancy is not resolved. You should probably see where I’m going. Yup, when the checkout process was stuck, it created discrepancies blocking the night audit process. There was no way of fixing them without doing migrations or code fixes. You couldn’t do a manual fix through the application (e.g. manually check out guest) because you couldn’t check out the blocked account. That’s the worst case that you could end up. You don’t want to be the person telling the customer that they cannot do anything and be fully operational until we update the software.</p> <p>Someone may say: <em>“Okay, there were some bugs in the requirements or in the code. We need to improve and they won’t happen again.”</em> But that’s not going to happen, we cannot assume that we won’t have a bug, or our hardware or network won’t have any random failure. Assuming that, it’s a romantic vision.</p> <p>Also, the reality is much more complex. It’s pretty common that one guest did a checkout but is still in the room. How? E.g. the door wasn’t closed, and the guest got back to the room to wait for a train, or there were a few people in the room. One was doing checkout; the other still stayed there.</p> <p>Or when we have a requirement, we have to check in a guest if we have an available room. What if it turns out that, in the meantime, the toilet broke and the room looks like a mess? What if we don’t have any other room available? How do we fulfil this requirement?</p> <p>Apart from Decalogue, only a few rules were carved in stone. Even Decalogue’s ten rules are not always applied properly in real life.</p> <p>Most computer systems rely on the automation of the already established “analogue” procedures. Such procedures usually allow many different options. There is a lot of grey area. We should design our systems accordingly. We should be asking the business:</p> <ul> <li>How is it being done now?</li> <li>What are you doing if this didn’t happen?</li> <li>How do you fix this issue?</li> </ul> <p>It often turns out that alternative flows exist.</p> <p>We, as programmers, try to create ideal systems, except that our definition of the ideal is somewhat distorted. The ideal system reflects the expected business process. It should embrace that things fail, and unforeseen scenarios will pop out. Therefore, in our systems, we should not treat the error as persona non grata.</p> <p>The memorable image of “this is not a bug, this is a feature” has a lot of truth in it. We should expect and prepare for many unexpected things in the real world. If we treat these scenarios as possible, we can also start talking to the business about handling them and treating them as requirements. Thanks to this, our software will be more convenient because it will have fewer dead ends. Users may be able to perform the corrective operation without being blocked. It will also be better for us, as we won’t need to be sending hotfixes in a rush (or at least less often).</p> <p>Therefore, when a business tells us that something will never happen, let’s answer: Okay, so how often?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If you liked this article, check <a href="/en/bring_me_problems_not_solutions/">Bring me problems, not solutions!</a>.</p><![CDATA[Integrating Marten with other systems]]>https://event-driven.io/en/integrating_Marten/https://event-driven.io/en/integrating_Marten/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/82a793a558bac66e1404f26e6c5d721d/d2429/2021-12-29-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACj0lEQVQoz02Ma0hTARzFL2QWUVBBiCYqEcZQtGAUWpCWbSNziUZB9ME0g9By5jCNmJJYlhs+yseYmjFtZkVOEUphan6wUqdX93Tu5ZZud3NuWlt39/EPTaLfh3M4nMNBAGgAmv5r24EGgA27bYhXUJ+VoZZJzLJWz8SwQ6+jSIqmaYraVmRzv7Xe/viPL42iymyusU9mfduxMf89sLEOACRJAgBFUQCAAJAAFEGSxBY4jm8e0ZsdKu/LOcVU9Us9io/e+akgjvvcqwRBeDCMJElseQUJeLuCPolCbR9ArcOosXtszh/wA4BcP36vpzKv/IZ6aNA7Ohywm95Ku1IZjIc8XlpCYllBYcrRWAR81722W72GtW4T3b1IyBYCbt860FCoESfrHqXoBK1TnQ7VHADw+SUIgoSEhCD/IJw5zsXbnVMWybRHPOnsnF2bm/yq+tybP/2SOV50RsFjjtzt+NTuGOkvu1+8A0Ei9u45GLrzwK7QsN2hiHHpqtqQNaHNG1PmaptTf0jS7I1MQ2W0UqNAMQtqVas8rpnXxbai/fbqRENJmOlBlIEfqS+JsJRGI/rlK/rlzEUsQ2fm+Jui4UU4NIXTzTE/F779+k1QAb9jFfcO8EG4jxZFgjAK6o6AMAaeH4baGESnTUdRzuwsG1WyTbITjjeJg20pcim3Sl6d2/9Y2DOQlN/Y0iB6VXpt9FmqtS3W0hG/2B6vETO0kjgEVi+DOx3cbHBzYI0VdHFu6mvOLrWwzHUcdUVCjZRR64oTWQ5VYA3vBeBNxldYlDONcp6nnOcQypVNuTIp7CKJccDJCtpZd2YEl4z1XIOIq6k63fTh+BPzyafKYwKd+F05OJNwK5tcukDaWKSN9QdgcsUAGPMt9gAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/82a793a558bac66e1404f26e6c5d721d/a331c/2021-12-29-cover.png" srcset="/static/82a793a558bac66e1404f26e6c5d721d/36ca5/2021-12-29-cover.png 200w, /static/82a793a558bac66e1404f26e6c5d721d/a3397/2021-12-29-cover.png 400w, /static/82a793a558bac66e1404f26e6c5d721d/a331c/2021-12-29-cover.png 800w, /static/82a793a558bac66e1404f26e6c5d721d/d2429/2021-12-29-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In Event Sourcing, events are the source of truth. We save them in the event store to have a permanent history of facts about business operations. Storing events and using them as an application state opens many possibilities but is not a killer feature in itself.</p> <p>Greg Young said that if you’re using Event Sourcing and you only use one database, then you are not using the potential of Event Sourcing. In my opinion, this is a bit exaggerated statement, but there is truth in it. If you’re using <a href="https://martendb.io/">Marten</a>, you can use only Postgres. Events and read models data are stored as JSON thanks to brilliant JSON support in Postgres. Using events enables further event-driven integrations and storage tuned to your needs—for example, most in the relational database and text filtering in Elastic Search.</p> <p>Of course, there are many challenges here. For example, to avoid leaking business logic, we should divide our events into internal (module-scoped_ external (system-scoped). We follow the straight highway to the distributed monolith if we do not do this. It is a dark place. I’ve been there, and I do not recommend it (to say mildly). I wrote about these considerations in the article <a href="/en/events_should_be_as_small_as_possible/">“Events should be as small as possible, right?”</a>.</p> <p>Another issue is delivery guarantees. Contrary to common impression, it is not easy to guarantee messages from one place to another. The Outbox pattern can help on that; I wrote about it longer in <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">“Outbox, Inbox patterns and delivery guarantees explained”</a>. In short, we’re storing events together with the state change (in the same transaction/atomic operation). Then the event is posted asynchronously. If message sending fails, it will be retried. Retries can cause sending messages multiple times. Therefore, we gain the at-least-once delivery guarantee. Our recipient must ensure the idempotent handling logic so that no unforeseen effects, such as duplicates, will be caused by processing the same event multiple times.</p> <p>We had a long undocumented (but used by our brave users) feature called “Async Daemon” in Marten. Its main task is to apply events using projections to read models asynchronously. In the recently released version 4, this mechanism has undergone a complete overhaul. It boosted performance and stability. It turns out that we can use it not only for projection logic but also for publishing events, e.g. on the event bus (Kafka, RabbitMQ, etc.) or for applying projections to databases other than Postgres.</p> <p>How can this be done? By defining your projection and connecting it to the Async Daemon mechanism.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MartenSubscription</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IProjection</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IMartenEventsConsumer</span> consumer<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">MartenSubscription</span><span class="token punctuation">(</span><span class="token class-name">IMartenEventsConsumer</span> consumer<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>consumer <span class="token operator">=</span> consumer<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span> <span class="token class-name">IDocumentOperations</span> operations<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streams <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NotSupportedException</span><span class="token punctuation">(</span><span class="token string">"Subscription should be only run asynchronously"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">ApplyAsync</span><span class="token punctuation">(</span> <span class="token class-name">IDocumentOperations</span> operations<span class="token punctuation">,</span> <span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streams<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> consumer<span class="token punctuation">.</span><span class="token function">ConsumeAsync</span><span class="token punctuation">(</span>streams<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>This interface requires the implementation of two event application operations. One is synchronous; the other is asynchronous. We’ll implement only the asynchronous version, as we don’t want those operations to run in the same database transaction as appending event. The key here is the <em>IReadOnlyList <StreamAction> streams</em>. It represents events grouped by streams that are currently being processed.</p> <p>What is <em>IMartenEventsConsumer</em>? It can be a client or our wrapper for tools we integrate.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IMartenEventsConsumer</span> <span class="token punctuation">{</span> <span class="token return-type class-name">Task</span> <span class="token function">ConsumeAsync</span><span class="token punctuation">(</span><span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streamActions<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The dumbest implementation to throw event data on the screen would look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">MartenEventsConsumer</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMartenEventsConsumer</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">List<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">></span></span> Events <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">ConsumeAsync</span><span class="token punctuation">(</span><span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streamActions<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> streamActions<span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>streamAction <span class="token operator">=></span> streamAction<span class="token punctuation">.</span>Events<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Events<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Console<span class="token punctuation">.</span><span class="token function">WriteLine</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">@<span class="token keyword">event</span><span class="token punctuation">.</span>Sequence</span><span class="token punctuation">}</span></span><span class="token string"> - </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">@<span class="token keyword">event</span><span class="token punctuation">.</span>EventTypeName</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> Task<span class="token punctuation">.</span>CompletedTask<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>To use it, you just need to register such a projection.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token function">AddMarten</span><span class="token punctuation">(</span>x <span class="token operator">=></span> <span class="token punctuation">{</span> x<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MartenSubscription</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">MartenEventsConsumer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> ProjectionLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">,</span> <span class="token string">"customConsumer"</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span></code></pre></div> <p>More real-world examples? Like Kafka producer? Here you have it:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">KafkaProducer</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IMartenEventsConsumer</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">KafkaProducerConfig</span> config<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">KafkaProducer</span><span class="token punctuation">(</span><span class="token class-name">KafkaProducerConfig</span> config<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>config <span class="token operator">=</span> config<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">ConsumeAsync</span><span class="token punctuation">(</span><span class="token class-name">IReadOnlyList<span class="token punctuation">&lt;</span>StreamAction<span class="token punctuation">></span></span> streamActions<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">using</span> <span class="token class-name"><span class="token keyword">var</span></span> kafkaProducer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProducerBuilder<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>ProducerConfig<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Build</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> streamActions<span class="token punctuation">.</span><span class="token function">SelectMany</span><span class="token punctuation">(</span>streamAction <span class="token operator">=></span> streamAction<span class="token punctuation">.</span>Events<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> kafkaProducer<span class="token punctuation">.</span><span class="token function">ProduceAsync</span><span class="token punctuation">(</span>config<span class="token punctuation">.</span>Topic<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Message<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> <span class="token keyword">string</span><span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token comment">// store event type name in message Key</span> Key <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span><span class="token function">GetType</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name<span class="token punctuation">,</span> <span class="token comment">// serialize event to message Value</span> Value <span class="token operator">=</span> JsonConvert<span class="token punctuation">.</span><span class="token function">SerializeObject</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">KafkaProducerConfig</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">ProducerConfig<span class="token punctuation">?</span></span> ProducerConfig <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Topic <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Async Daemon guarantees at-least delivery. It’s pull-based, so Marten internally queries the database continuously for new events. Because of that, it may use more resources than push-based solutions, but as it’s internally doing batching, it should be a good entry point solution for Marten-based systems.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Are you using EventStoreDB or curious how that works there? Check my another article <a href="/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/">Persistent vs catch-up, EventStoreDB subscriptions in action</a>. TLDR on the differences? Marten subscription (<em>IProjection</em>) is hard to map precisely. The closest to say is that it’s like a catch-up subscription to <em>$all</em>. Though it has retries of transient errors, it has built-in checkpointing (like a persistent subscription). Marten projection doesn’t allow you to subscribe from a certain position. You need to do it from the beginning.</p><![CDATA[How to do snapshots in Marten?]]>https://event-driven.io/en/how_to_do_snapshots_in_Marten/https://event-driven.io/en/how_to_do_snapshots_in_Marten/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/fb80596d4df90a1b70a30f755f8149d2/d2429/2021-12-22-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACHElEQVQozx2SS2sTYRiFz/t+t5n5vrlmZkwmbTI1vVIaYtuY2mIqsRZKq9JWuiiKgiC40ILifyi4UFy4cONe8fITZYSzfXg4nANUBo4RCggGmLSCEABATUgK6SkE/xMpVBKJpJCoIGgAnoEANDUwSRJyulkez8YnJ0enB6PD/RUQs/UgCbFAqVH40AwN0cBCgRsnWIClNv7aQvz43q3fXz9+ujq7N65htEocfI+NaPxOmIRtRGmLQYUkJQAJlpBGaa/biZxzs63Jq7OTsgzL3PdSqzJLWYCAKSKbsl+wiAUQyKYhmFgBIondxf3iRop6rn52erjcN2Xuu9jYUPgd7WXS9TzXlzQ0iAhhYkPrETVypfRwqXhz3n6xhz/X+z+/fdjomyLzAydcSFlLlF2te5EaJ2bPU8uEvErLKu92M9Z6dy0bDrL97fz7Vfnr+u6PL+8Wq8A3Mk1kmnLs2A9JlU53UlmllDCENelccDi7Od7qbQ/CorC7w/DlUX40Xfr7+X0/d2Xily3tHFkng/lscjzdmE2T5brZqOqbuRXz9GL94YPa+SLwzXzbgTFeLN4ebyZWKUW+JS3hdb2ty8nk/CAe1NwMTMg7an7VjHai8Z1UG7ZKlYFm6e3U7Se36yhlYZg9EVgRVByPgnjQAnFzIsFoVbbVMwvrQdFnlpRmYdUOR6Pu6+ezy0eb9WoByRBMmokJgEy1bhkwCy3/AUB4OW6OeN0kAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/fb80596d4df90a1b70a30f755f8149d2/a331c/2021-12-22-cover.png" srcset="/static/fb80596d4df90a1b70a30f755f8149d2/36ca5/2021-12-22-cover.png 200w, /static/fb80596d4df90a1b70a30f755f8149d2/a3397/2021-12-22-cover.png 400w, /static/fb80596d4df90a1b70a30f755f8149d2/a331c/2021-12-22-cover.png 800w, /static/fb80596d4df90a1b70a30f755f8149d2/d2429/2021-12-22-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Getting the state from events is a basic but controversial topic in Event Sourcing. I wrote on it longer in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">other article</a>. To recap, Classically, in Event Sourcing, we create a default object and apply events from the stream in the order of appearance. In this way, our events are actually the source of truth. That’s <a href="/en/event_streaming_is_not_event_sourcing/">contrary to the Event Streaming approach</a> where we’re getting the current state from the materialised view.</p> <p><a href="https://martendb.io/events/">Marten</a>, of course, allows this default behaviour. We have a built-in method <em>AggregateStream</em> that does all the needed steps. We can apply events both to the default and to a non-empty object. It can be helpful, for example, when taking snapshots. In short, a snapshot is the state of a stream at a specific point in time. They are used to optimise performance. They should not be the first choice, but they are an option for performance-critical functionality.</p> <p>Read more in my other articles on why you may not need Snapshots and/or what are the general strategies of dealing with them:</p> <ul> <li><a href="https://www.eventstore.com/blog/snapshots-in-event-sourcing">Snapshots in Event Sourcing</a>,</li> <li><a href="https://www.eventstore.com/blog/snapshotting-strategies">Snapshotting Strategies</a>,</li> <li><a href="https://www.eventstore.com/blog/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention">Keep your streams short! Temporal modeling for fast reads and optimal data retention</a>.</li> </ul> <p>However, if you decide to use snapshots as a performance optimisation, be careful to not make things worse. If you want to do a snapshot after each event append, you can speed up the readings but significantly slow down the write side. As a middle ground, you could do a snapshot once per a set of events, e.g. after a specific event type, periodically, or a number of events.</p> <p>Let’s take a financial account as an example:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AccountingMonthOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> FinancialAccountId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Month<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Year<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> StartingBalance <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">InflowRecorded</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> FinancialAccountId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> TransactionAmount <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CashWithdrawnFromATM</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> FinancialAccountId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> CashAmount <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AccountingMonthClosed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> FinancialAccountId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Month<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Year<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> FinalBalance <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FinancialAccount</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> CurrentMonth <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> CurrentYear <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsOpened <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> Balance <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Version <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">AccountingMonthOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>FinancialAccountId<span class="token punctuation">;</span> CurrentMonth <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Month<span class="token punctuation">;</span> CurrentYear <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Year<span class="token punctuation">;</span> Balance <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>StartingBalance<span class="token punctuation">;</span> IsOpened <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span> Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">InflowRecorded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Balance <span class="token operator">+=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>TransactionAmount<span class="token punctuation">;</span> Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">CashWithdrawnFromATM</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Balance <span class="token operator">-=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CashAmount<span class="token punctuation">;</span> Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">AccountingMonthClosed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> IsOpened <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span> Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Marten enables snapshotting after each event with such one liner in the configuration:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> store <span class="token operator">=</span> DocumentStore<span class="token punctuation">.</span><span class="token function">For</span><span class="token punctuation">(</span>opts <span class="token operator">=></span> <span class="token punctuation">{</span> opts<span class="token punctuation">.</span><span class="token function">Connection</span><span class="token punctuation">(</span><span class="token string">"some connection string"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Run the Trip as an inline projection</span> opts<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Snapshot</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>FinancialAccount<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>SnapshotLifecycle<span class="token punctuation">.</span>Inline<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Or run it as an asynchronous projection</span> opts<span class="token punctuation">.</span>Projections<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Snapshot</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>FinancialAccount<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>SnapshotLifecycle<span class="token punctuation">.</span>Async<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The inline mode will create a snapshot in the same transaction as appending event, async in the background process.</p> <p>However, doing that at each event might not be the most effective way. Let’s see how you could do that once per a few events.</p> <p>You don’t need to know your entire account history for the day-to-day transaction processing. It is enough to have information about the current billing period, e.g. a month. It may be worth taking a snapshot after opening a new billing period for such a scenario. We could use it as the initial object state to apply subsequent transaction events. We could do this by defining a wrapper class like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FinancialAccountRepository</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token class-name">IDocumentSession</span> session<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">FinancialAccountRepository</span><span class="token punctuation">(</span><span class="token class-name">IDocumentSession</span> session<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>session <span class="token operator">=</span> session<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">Store</span><span class="token punctuation">(</span> <span class="token class-name">FinancialAccount</span> financialAccount<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token operator">=</span> <span class="token keyword">default</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token keyword">is</span> <span class="token class-name">AccountingMonthOpened</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> session<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>financialAccount<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">Append</span><span class="token punctuation">(</span>financialAccount<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> session<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>FinancialAccount<span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token function">Get</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> cashRegisterId<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token operator">=</span> <span class="token keyword">default</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> cashRegister <span class="token operator">=</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LoadAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>FinancialAccount<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>cashRegisterId<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> fromVersion <span class="token operator">=</span> cashRegister <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token punctuation">?</span> <span class="token comment">// incrementing version to not apply the same event twice</span> cashRegister<span class="token punctuation">.</span>Version <span class="token operator">+</span> <span class="token number">1</span> <span class="token punctuation">:</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">await</span> session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token function">AggregateStreamAsync</span><span class="token punctuation">(</span> cashRegisterId<span class="token punctuation">,</span> <span class="token named-parameter punctuation">state</span><span class="token punctuation">:</span> cashRegister<span class="token punctuation">,</span> <span class="token named-parameter punctuation">fromVersion</span><span class="token punctuation">:</span> fromVersion<span class="token punctuation">,</span> <span class="token named-parameter punctuation">token</span><span class="token punctuation">:</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then add an event and save the snapshot at the opening of the billing month:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token return-type class-name"><span class="token punctuation">(</span>FinancialAccount<span class="token punctuation">,</span> AccountingMonthOpened<span class="token punctuation">)</span></span> <span class="token function">OpenAccountingMonth</span><span class="token punctuation">(</span><span class="token class-name">FinancialAccount</span> cashRegister<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">AccountingMonthOpened</span><span class="token punctuation">(</span>cashRegister<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> <span class="token number">11</span><span class="token punctuation">,</span> <span class="token number">2021</span><span class="token punctuation">,</span> <span class="token number">300</span><span class="token punctuation">)</span><span class="token punctuation">;</span> cashRegister<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>cashRegister<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token class-name"><span class="token keyword">var</span></span> closedCashierShift <span class="token operator">=</span> <span class="token keyword">await</span> theSession<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AggregateStreamAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>FinancialAccount<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> financialAccountId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>openedCashierShift<span class="token punctuation">,</span> cashierShiftOpened<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token function">OpenAccountingMonth</span><span class="token punctuation">(</span>closedCashierShift<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> repository <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CashRegisterRepository</span><span class="token punctuation">(</span>_session<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">Store</span><span class="token punctuation">(</span>openedCashierShift<span class="token punctuation">,</span> cashierShiftOpened<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>and load a snapshot and events that were recorded after it was created by calling it</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> currentState <span class="token operator">=</span> <span class="token keyword">await</span> repository<span class="token punctuation">.</span><span class="token function">Get</span><span class="token punctuation">(</span>financialAccountId<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Of course, be warned that this should not be our first-choice option. Event stores usually cope with downloading several dozen or even more events. If we want to optimise something, let’s make sure that we really have to do it and the exact requirements that we have to meet.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[CQRS is simpler than you think with .NET 6 and C# 10]]>https://event-driven.io/en/cqrs_is_simpler_than_you_think_with_net6/https://event-driven.io/en/cqrs_is_simpler_than_you_think_with_net6/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a04900fddfddf7ddf03fbe189e50701e/d2429/2021-12-15-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADQ0lEQVQozx2SaUyTBwCGv3/LbnXJsv1ZNogzk0RQYdNNzaZzbtFtCSaTuSUCSYGWQxnHCvSCHgKtPSi0hYBQ140BlRVdHYLlaKEDQeVsyqSCR6JhmYQp0W3wvgv8ePLm+f0+wuuvvcq3N0dhe+xW7N0dj/cS4vBufBw+2B2/vgf378GRzw4gKjoKz7zwIp596WU8t2Ejnt+4CXE7t0Oalwm9RoqaM6XYERsDwWIow+rf8+j5rRUV5SrcGOrC6tIcVpZuYXkhzH8e3gQfzePWdBAOmxEpolRIssVodtVhYf4G+OQeVpdmydX7OJZ4GEJddSX+XYzQ236OdXYjtVo5H0RG+fTPGT5ZYyHMx/enubIYIR/fJpfvkI/myaf3uLI4y+UHITycv8b//ppB0tEjEKqNGvR2uWk16+BqqkFBYQ6uXGrF3Zlh/DHuZ2RqEHOhId6cGMD0aA+G/V5MXPXR1+nG5EgPw2MB/N53kXOhIE4cT4Rg0MngcFgYE5+AQ198zj0H9tNoqqTRVE6LRU9LlZ4mUwXb287xB2ct8wq/o91uYmaOhLrTpdRolZArixgeD0CUfAyCougUfN0X6HE7MTniw1D/r7zoaaaqTEaFSkaNVsWsk5kskUvpcJgpVxZTb9CyvFLNEvn3VJaWoLa2ijOTQUhE30LIy0nDxHU/f/6xAb2Xz/PCLy76ujz093rZ1tLEhgYb290u2mqMdLc66Wxy0G4309loY1tLI3u7O9DX7UF4fIC5makQsjOSMTbSh1q7mQ31NTjf6sRPrno0nrXBYTfDbjOhuvoMrFYDrVYDWpoboNMpodEooDutgrQoH3q9Bn5fB2UFEgjpqccx2H8JHx76hGmSdJRXqJmRlc7EpK8oEosozspgmUZBaXEBJDlianUKFkhz15xqrZJFskJaqyrZ7+ugWpYLQZTyNe7OXkezq56XvW3ou+LB1YFOjAx2YjjgxdS1HkSmg2usPxke60dkagB3wkFEJv2YmwogNNqN26EAivPFEBJ2bkOFWorS4lOQFWYh/2QasjNOID0lCcnfHF2P9cvDH+PTg/vw0b5d2Pt+PHYlxGJH7FZsi9mCd7ZEY3P0m4h+6w28smkD/ge93VOmmgXC5AAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/a04900fddfddf7ddf03fbe189e50701e/a331c/2021-12-15-cover.png" srcset="/static/a04900fddfddf7ddf03fbe189e50701e/36ca5/2021-12-15-cover.png 200w, /static/a04900fddfddf7ddf03fbe189e50701e/a3397/2021-12-15-cover.png 400w, /static/a04900fddfddf7ddf03fbe189e50701e/a331c/2021-12-15-cover.png 800w, /static/a04900fddfddf7ddf03fbe189e50701e/d2429/2021-12-15-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>.NET and CQRS are well known for the high ceremony and enterprise feeling. You should treat that as superstition. Let me tell you why.</p> <p>CQRS is a pattern where we’re segregating application behaviours. We’re splitting them into command and queries. Commands are intents to do something (e.g. change the state). Queries should return data but do not create side effects. Just like a question should not change the answer. Simple as that. We’re slicing our business domain vertically by operations we can do on it. The split can help us to focus on the specific business operation, reduce complexity and cognitive load, enable optimisations and scaling. Read more in:</p> <ul> <li><a href="/en/cqrs_facts_and_myths_explained/">CQRS facts and myths explained</a>,</li> <li><a href="/en/generic_does_not_mean_simple/">Generic does not mean Simple</a></li> <li><a href="/en/how_to_slice_the_codebase_effectively/">How to slice the codebase effectively?</a></li> </ul> <p>.NET ceremony is a more challenging beast. F# since the beginning was focused on succinct syntax. The community around it took this attitude to their tools. Unfortunately, that didn’t get into the mainstream. By mainstream, I mean C# language and .NET. It’s undeniable that .NET originates from Microsoft come from. Big enterprise clients. It went a hell of a journey from untestable Windows framework to cross-platform Open Source solution. C# is a language that’s quickly changing and adopting trends. It’s a Frankenstein. You can love it or hate it, but nowadays, you can write in it in almost any paradigm.</p> <p>I wrote some time ago that <a href="/en/will_it_scale_down/">we should be more concerned about scaling down than scaling up</a>. It can be visible also in .NET. The core team is highly focused on performance improvements and cutting ceremonies. In .NET 6 they gave us “Minimal APIs”. It’s a funny name because it seems that what we call <em>“minimal”</em> in the .NET space, it’s rather regular in environments like NodeJS, Go, etc. Still, better later than never! Let’s see how we can use it to make CQRS great again.</p> <p>Let’s start by defining primitives for our command and query handling:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ICommandHandler<span class="token punctuation">&lt;</span><span class="token keyword">in</span> T<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token return-type class-name">ValueTask</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">T</span> command<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IQueryHandler<span class="token punctuation">&lt;</span><span class="token keyword">in</span> T<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>TResult<span class="token punctuation">></span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">T</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, the command handler needs to have a method that takes the command object and performs the operation. <em>ValueTask</em> means that we’re unsure if handling will be asynchronous.</p> <p>The query handler takes the query object and returns the result (also wrapped in <em>ValueTask</em>).</p> <p>We could skip this interface, but C# does not allow function without classes, so we’ll still have to create the class even if we use just static handlers and pure functions. Plus, we don’t want to make a revolution here: C# devs love DI, don’t they?</p> <p>As we’re making our services smaller and smaller, sometimes even reaching micro-scale, the approach can be handy. .NET 6 and Minimal APIs enables simplified syntax. We can define our API in a single file. Of course, that won’t work well for a massive monolith, but if we focus on the proper boundaries and split our API and modules per feature or domain object, then that could work pretty well. We’ll start with defining <em>Program.cs</em> file.</p> <p>At first, we’ll define Web Application and register services:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> builder <span class="token operator">=</span> WebApplication<span class="token punctuation">.</span><span class="token function">CreateBuilder</span><span class="token punctuation">(</span>args<span class="token punctuation">)</span><span class="token punctuation">;</span> builder<span class="token punctuation">.</span>Services <span class="token punctuation">.</span><span class="token function">AddEndpointsApiExplorer</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddSwaggerGen</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddDbContext</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>WarehouseDBContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> options <span class="token operator">=></span> options<span class="token punctuation">.</span><span class="token function">UseNpgsql</span><span class="token punctuation">(</span><span class="token string">"name=ConnectionStrings:WarehouseDB"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddProductServices</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">UseExceptionHandlingMiddleware</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>app<span class="token punctuation">.</span>Environment<span class="token punctuation">.</span><span class="token function">IsDevelopment</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> app<span class="token punctuation">.</span><span class="token function">UseSwagger</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">UseSwaggerUI</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> app<span class="token punctuation">.</span><span class="token function">Run</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We already may observe two things:</p> <ul> <li>Minimal APIs support Swagger (that wasn’t the case in .NET 5 for Endpoints),</li> <li>Minimal APIs support regular middlewares (e.g. for mapping exceptions to HTTP statuses),</li> <li>We’ll be using Entity Framework, because why not? I’ll use the most CRUDish example to focus on CQRS segregation and show that you can benefit from CQRS and vertical slices even there.</li> </ul> <p>Okay, but how to define Minimal APIs? Like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// Get Products</span> app<span class="token punctuation">.</span><span class="token function">MapGet</span><span class="token punctuation">(</span><span class="token string">"/api/products"</span><span class="token punctuation">,</span> HandleGetProducts<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>HttpStatusCode<span class="token punctuation">.</span>BadRequest<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">HandleGetProducts</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">QueryHandler<span class="token punctuation">&lt;</span>GetProducts<span class="token punctuation">,</span> IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span> getProducts<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> filter<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> page<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> pageSize<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">getProducts</span><span class="token punctuation">(</span>GetProducts<span class="token punctuation">.</span><span class="token function">With</span><span class="token punctuation">(</span>filter<span class="token punctuation">,</span> page<span class="token punctuation">,</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Get Product Details by Id</span> app<span class="token punctuation">.</span><span class="token function">MapGet</span><span class="token punctuation">(</span><span class="token string">"/api/products/{id}"</span><span class="token punctuation">,</span> HandleGetProductDetails<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span>StatusCodes<span class="token punctuation">.</span>Status400BadRequest<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span>StatusCodes<span class="token punctuation">.</span>Status404NotFound<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>IResult<span class="token punctuation">></span></span> <span class="token function">HandleGetProductDetails</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">QueryHandler<span class="token punctuation">&lt;</span>GetProductDetails<span class="token punctuation">,</span> ProductDetails<span class="token punctuation">?</span><span class="token punctuation">></span></span> getProductById<span class="token punctuation">,</span> <span class="token class-name">Guid</span> productId<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">await</span> <span class="token function">getProductById</span><span class="token punctuation">(</span>GetProductDetails<span class="token punctuation">.</span><span class="token function">With</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token keyword">is</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token return-type class-name">product <span class="token punctuation">?</span></span> Results<span class="token punctuation">.</span><span class="token function">Ok</span><span class="token punctuation">(</span>product<span class="token punctuation">)</span> <span class="token punctuation">:</span> Results<span class="token punctuation">.</span><span class="token function">NotFound</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Register new product</span> app<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"api/products/"</span><span class="token punctuation">,</span>HandleRegisterProduct<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span>StatusCodes<span class="token punctuation">.</span>Status400BadRequest<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span>StatusCodes<span class="token punctuation">.</span>Status404NotFound<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>IResult<span class="token punctuation">></span></span> <span class="token function">HandleRegisterProduct</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">CommandHandler<span class="token punctuation">&lt;</span>RegisterProduct<span class="token punctuation">></span></span> registerProduct<span class="token punctuation">,</span> <span class="token class-name">RegisterProductRequest</span> request<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span> <span class="token operator">=</span> request<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">registerProduct</span><span class="token punctuation">(</span> RegisterProduct<span class="token punctuation">.</span><span class="token function">With</span><span class="token punctuation">(</span>productId<span class="token punctuation">,</span> sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> Results<span class="token punctuation">.</span><span class="token function">Created</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/products/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>WebApplication builder enables endpoint definition. We can use <em>MapGet</em>, <em>MapPost</em> etc., for each of the HTTP methods. They have:</p> <ul> <li>the same routing capabilities as regular Controllers,</li> <li>model binding,</li> <li>dependency injection etc.</li> </ul> <p>You can define endpoint using inline lambda, but when we have more parameters (like in filter method), it becomes less readable for me. As we can define OpenAPI specification, it’s getting even more blurry. That’s why I prefer a separate function put right after the endpoint definition. .NET 6 enables to set function without classes in <em>Program.cs</em> file (they’re, in fact, local functions). Minimal APIs also provide pretty smart parameters binding. They will automatically match function parameters from query, route or body.</p> <p>You can still mark them explicitly by attributes, but you may not need them. You might have noticed that I’m using the <em>FromServices</em> attribute, together with types that we don’t know yet:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">QueryHandler<span class="token punctuation">&lt;</span>GetProducts<span class="token punctuation">,</span> IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span> getProducts<span class="token punctuation">,</span> <span class="token comment">// (...)</span> <span class="token punctuation">[</span>FromServices<span class="token punctuation">]</span> <span class="token class-name">QueryHandler<span class="token punctuation">&lt;</span>GetProductDetails<span class="token punctuation">,</span> ProductDetails<span class="token punctuation">?</span><span class="token punctuation">></span></span> getProductById<span class="token punctuation">,</span> <span class="token comment">// (...)</span> <span class="token punctuation">[</span>FromServices<span class="token punctuation">]</span> <span class="token class-name">CommandHandler<span class="token punctuation">&lt;</span>RegisterProduct<span class="token punctuation">></span></span> registerProduct<span class="token punctuation">,</span></code></pre></div> <p>That’s my optimisation. I could just inject <em>IQueryHandler&#x3C;GetProducts, IReadOnlyList<ProductListItem></em>, but then I’d need to use the weird syntax:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">handler<span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>GetProducts<span class="token punctuation">.</span><span class="token function">With</span><span class="token punctuation">(</span>filter<span class="token punctuation">,</span> page<span class="token punctuation">,</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>As I’d prefer just to use function then I defined the following delegate and registration extension methods:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">delegate</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>TResult<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">QueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">in</span> T<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">T</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">QueryHandlerConfiguration</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token generic-method"><span class="token function">AddQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TResult<span class="token punctuation">,</span> TQueryHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TQueryHandler</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">IQueryHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> services <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IQueryHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TResult<span class="token punctuation">></span><span class="token punctuation">,</span> TQueryHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>QueryHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TResult<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span> sp <span class="token operator">=></span> sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IQueryHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TResult<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Handle <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> services<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>I’m defined shortened delegate for the handler function that registers both <em>IQueryHandler</em> and handler function delegate. This, in the future, can enable me to decorate handler resolution and provide MediatR-like pipelines. Read more in the <a href="/en/how_to_register_all_mediatr_handlers_by_convention/">How to register all CQRS handlers by convention</a>. The same can be done accordingly for Commands.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">delegate</span> <span class="token return-type class-name">ValueTask</span> <span class="token generic-method"><span class="token function">CommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">in</span> T<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">T</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CommandHandlerConfiguration</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token generic-method"><span class="token function">AddCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TCommandHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TCommandHandler</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">ICommandHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> services <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IQueryHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">,</span> TCommandHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>QueryHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span> sp <span class="token operator">=></span> sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ICommandHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Handle <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> services<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Thanks to that, I can get simple query handling definition:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">HandleGetProducts</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">QueryHandler<span class="token punctuation">&lt;</span>GetProducts<span class="token punctuation">,</span> IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span> getProducts<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> filter<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> page<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> pageSize<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">getProducts</span><span class="token punctuation">(</span>GetProducts<span class="token punctuation">.</span><span class="token function">With</span><span class="token punctuation">(</span>filter<span class="token punctuation">,</span> page<span class="token punctuation">,</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Using delegate type instead of raw <em>Fun</em> will also help me not accidentally to do wrong function definition or registration. The compiler will help me with that.</p> <p>Many examples of the Minimal APIs shows all code in the same file. Frankly, I don’t see that being maintainable in the production code in the long term. We still should have a split for domain logic and application code. The design should be simple, but not simpler. Thus, I’d prefer to have just pure domain code in command handling. Something around that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">HandleRegisterProduct</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ICommandHandler<span class="token punctuation">&lt;</span>RegisterProduct<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>Product<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> ValueTask<span class="token punctuation">></span></span> addProduct<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>SKU<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> ValueTask<span class="token punctuation">&lt;</span><span class="token keyword">bool</span><span class="token punctuation">></span><span class="token punctuation">></span></span> productWithSKUExists<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">HandleRegisterProduct</span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>Product<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> ValueTask<span class="token punctuation">></span></span> addProduct<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>SKU<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> ValueTask<span class="token punctuation">&lt;</span><span class="token keyword">bool</span><span class="token punctuation">></span><span class="token punctuation">></span></span> productWithSKUExists <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addProduct <span class="token operator">=</span> addProduct<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>productWithSKUExists <span class="token operator">=</span> productWithSKUExists<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">ValueTask</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">RegisterProduct</span> command<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> product <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Product</span><span class="token punctuation">(</span> command<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>SKU<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Description <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">productWithSKUExists</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>SKU<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Product with SKU `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">command<span class="token punctuation">.</span>SKU</span><span class="token punctuation">}</span></span><span class="token string"> already exists."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">addProduct</span><span class="token punctuation">(</span>product<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RegisterProduct</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProductId<span class="token punctuation">,</span> <span class="token class-name">SKU</span> SKU<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">RegisterProduct</span> <span class="token function">With</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> sku<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> description<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>id<span class="token punctuation">.</span>HasValue <span class="token operator">||</span> id <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>description <span class="token keyword">is</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RegisterProduct</span><span class="token punctuation">(</span>id<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> SKU<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see, I don’t have direct references to frameworks (like EntityFramework) but pure C# code. I’m injecting functions that are doing specific stuff. If you prefer, you can use Aggregates instead or other DDD structures, but if your domain is relatively simple, just entities and pure function should be fine. As you see, this code is easily testable.</p> <p>Okay, but how will it be connected to the real application code? We can either do plumbing directly in the endpoints definition or define it in the dedicated registration class. That’s what I’d do.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Configuration</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token function">AddProductServices</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token operator">=></span> services <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddQueryable</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Product<span class="token punctuation">,</span> WarehouseDBContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RegisterProduct<span class="token punctuation">,</span> HandleRegisterProduct<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>s <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> dbContext <span class="token operator">=</span> s<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>WarehouseDBContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">HandleRegisterProduct</span><span class="token punctuation">(</span>dbContext<span class="token punctuation">.</span>AddAndSave<span class="token punctuation">,</span> dbContext<span class="token punctuation">.</span>ProductWithSKUExists<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>GetProducts<span class="token punctuation">,</span> IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">,</span> HandleGetProducts<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>GetProductDetails<span class="token punctuation">,</span> ProductDetails<span class="token punctuation">?</span><span class="token punctuation">,</span> HandleGetProductDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span><span class="token keyword">bool</span><span class="token punctuation">></span></span> <span class="token function">ProductWithSKUExists</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">WarehouseDBContext</span> dbContext<span class="token punctuation">,</span> <span class="token class-name">SKU</span> productSKU<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token punctuation">(</span>dbContext<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Set</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Product<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AnyAsync</span><span class="token punctuation">(</span>product <span class="token operator">=></span> product<span class="token punctuation">.</span>Sku<span class="token punctuation">.</span>Value <span class="token operator">==</span> productSKU<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re using here also here a few additional extensions:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">ValueTask</span> <span class="token generic-method"><span class="token function">AddAndSave</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">DbContext</span> dbContext<span class="token punctuation">,</span> <span class="token class-name">T</span> entity<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>entity<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>T<span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Find</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TId<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">DbContext</span> dbContext<span class="token punctuation">,</span> <span class="token class-name">TId</span> id<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token keyword">where</span> <span class="token class-name">TId</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token operator">=></span> dbContext<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FindAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span>id<span class="token punctuation">}</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token generic-method"><span class="token function">AddQueryable</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TDbContext</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DbContext</span></span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token function">AddTransient</span><span class="token punctuation">(</span>sp <span class="token operator">=></span> sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TDbContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Set</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AsNoTracking</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Those simple methods enable us to compose our small pieces into a simple, testable, but scalable and maintainable design. Of course, if you prefer to inject <em>DbContext</em>, that’s may also be fine. However, I like to keep it that way, as, by that, I can have a clear split between the domain logic and application code. It’s easier for me to maintain the order. Also, by injecting specified classes, we’re doing design by capabilities. It is more explicit and secure, as I clearly define what I intend to do. If we inject the whole <em>DbContext</em>, we can do everything. With great power comes great responsibility. See more in a great Scott Wlaschin’s talk <a href="https://www.youtube.com/watch?v=fi1FsDW1QeY">Designing with Capabilities</a>.</p> <p>Let’s get back for a moment to the register product endpoint.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token comment">// Register new product</span> app<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"api/products/"</span><span class="token punctuation">,</span>HandleRegisterProduct<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span>StatusCodes<span class="token punctuation">.</span>Status400BadRequest<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Produces</span><span class="token punctuation">(</span>StatusCodes<span class="token punctuation">.</span>Status404NotFound<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>IResult<span class="token punctuation">></span></span> <span class="token function">HandleRegisterProduct</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">CommandHandler<span class="token punctuation">&lt;</span>RegisterProduct<span class="token punctuation">></span></span> registerProduct<span class="token punctuation">,</span> <span class="token class-name">RegisterProductRequest</span> request<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span> <span class="token operator">=</span> request<span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">registerProduct</span><span class="token punctuation">(</span> RegisterProduct<span class="token punctuation">.</span><span class="token function">With</span><span class="token punctuation">(</span>productId<span class="token punctuation">,</span> sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> Results<span class="token punctuation">.</span><span class="token function">Created</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"/api/products/</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">productId</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>You might have noticed that I’m not passing the request contract but mapping it into the command type.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RegisterProduct</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProductId<span class="token punctuation">,</span> <span class="token class-name">SKU</span> SKU<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">RegisterProduct</span> <span class="token function">With</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> sku<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> description<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>id<span class="token punctuation">.</span>HasValue <span class="token operator">||</span> id <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>description <span class="token keyword">is</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RegisterProduct</span><span class="token punctuation">(</span>id<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> SKU<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>I might have to do that, but then I’d need to validate it in the command handler code. I’m doing here a bit of the Type-Driven Development (again KUDOS to Scott Wlashin and his <a href="https://www.amazon.pl/Domain-Modeling-Made-Functional-Domain-Driven">Domain Modeling made Functional</a>). In my domain code, I want to be sure that what I get already fulfils the basic semantic rules. I don’t want to do multiple times validation if the SKU number is valid or not or if the product name is empty. Having the type that I can trust is a huge benefit, as it means:</p> <ul> <li>single source of truth,</li> <li>fewer IFs,</li> <li>less testing,</li> <li>predictable code.</li> </ul> <p>We need to remember that Nullable Reference Types and Records are just syntactic sugar on top of regular classes. We cannot trust them unless we create them in our code. Read more in <a href="/en/notes_about_csharp_records_and_nullable_reference_types/">Notes about C# records and Nullable Reference Types</a>.</p> <p>Even if we’re just using primitives, they have limited built-in validations. Thus it’s worth defining types like SKU that makes sure that our types are validating themselves and helping us to write concise code:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">SKU</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Value <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">JsonConstructor</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token function">SKU</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Value <span class="token operator">=</span> <span class="token keyword">value</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">SKU</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">value</span> <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>SKU<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrWhiteSpace</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span>Regex<span class="token punctuation">.</span><span class="token function">IsMatch</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">,</span> <span class="token string">"[A-Z]{2,4}[0-9]{4,18}"</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>SKU<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SKU</span><span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Interestingly, this is already supported in the Entity Framework, as <a href="https://docs.microsoft.com/en-us/ef/core/modeling/owned-entities">Owned Types</a>. We can use such types even inside our entity definition:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">Product</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">SKU</span> Sku <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">Product</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">Product</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">SKU</span> sku<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> description<span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> id<span class="token punctuation">;</span> Sku <span class="token operator">=</span> sku<span class="token punctuation">;</span> Name <span class="token operator">=</span> name<span class="token punctuation">;</span> Description <span class="token operator">=</span> description<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Unfortunately, the con is that we need to keep the default constructor to make it work. And of course, define it properly in the data model:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">WarehouseDBContext</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DbContext</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">WarehouseDBContext</span><span class="token punctuation">(</span><span class="token class-name">DbContextOptions<span class="token punctuation">&lt;</span>WarehouseDBContext<span class="token punctuation">></span></span> options<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">OnModelCreating</span><span class="token punctuation">(</span><span class="token class-name">ModelBuilder</span> modelBuilder<span class="token punctuation">)</span> <span class="token punctuation">{</span> modelBuilder<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Entity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Product<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">OwnsOne</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>Sku<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Let’s get back for the last time to the query handling.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">HandleGetProducts</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromServices</span></span><span class="token punctuation">]</span> <span class="token class-name">QueryHandler<span class="token punctuation">&lt;</span>GetProducts<span class="token punctuation">,</span> IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span> getProducts<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> filter<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> page<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> pageSize<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct <span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">getProducts</span><span class="token punctuation">(</span>GetProducts<span class="token punctuation">.</span><span class="token function">With</span><span class="token punctuation">(</span>filter<span class="token punctuation">,</span> page<span class="token punctuation">,</span> pageSize<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And it’s registration:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token generic-method"><span class="token function">AddQueryable</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TDbContext</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DbContext</span></span> <span class="token keyword">where</span> <span class="token class-name">T</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token operator">=></span> services<span class="token punctuation">.</span><span class="token function">AddTransient</span><span class="token punctuation">(</span>sp <span class="token operator">=></span> sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TDbContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Set</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">AsNoTracking</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// (...)</span> services <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddQueryable</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Product<span class="token punctuation">,</span> WarehouseDBContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>GetProducts<span class="token punctuation">,</span> IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">,</span> HandleGetProducts<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>GetProductDetails<span class="token punctuation">,</span> ProductDetails<span class="token punctuation">?</span><span class="token punctuation">,</span> HandleGetProductDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Because we know that query won’t be doing any changes, we can do automatic optimisations like disabling change tracking. Worth noticing is that CQRS doesn’t require a separate data model. You can use the same table for multiple queries, e.g. <em>GetProducts</em> to return a list with a subset of data:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">HandleGetProducts</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IQueryHandler<span class="token punctuation">&lt;</span>GetProducts<span class="token punctuation">,</span> IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IQueryable<span class="token punctuation">&lt;</span>Product<span class="token punctuation">></span></span> products<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">HandleGetProducts</span><span class="token punctuation">(</span><span class="token class-name">IQueryable<span class="token punctuation">&lt;</span>Product<span class="token punctuation">></span></span> products<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>products <span class="token operator">=</span> products<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>IReadOnlyList<span class="token punctuation">&lt;</span>ProductListItem<span class="token punctuation">></span><span class="token punctuation">></span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">GetProducts</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>filter<span class="token punctuation">,</span> page<span class="token punctuation">,</span> pageSize<span class="token punctuation">)</span> <span class="token operator">=</span> query<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> filteredProducts <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>filter<span class="token punctuation">)</span> <span class="token punctuation">?</span> products <span class="token punctuation">:</span> products <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>Sku<span class="token punctuation">.</span>Value<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>query<span class="token punctuation">.</span>Filter<span class="token operator">!</span><span class="token punctuation">)</span> <span class="token operator">||</span> p<span class="token punctuation">.</span>Name<span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>query<span class="token punctuation">.</span>Filter<span class="token operator">!</span><span class="token punctuation">)</span> <span class="token operator">||</span> p<span class="token punctuation">.</span>Description<span class="token operator">!</span><span class="token punctuation">.</span><span class="token function">Contains</span><span class="token punctuation">(</span>query<span class="token punctuation">.</span>Filter<span class="token operator">!</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">await</span> filteredProducts <span class="token punctuation">.</span><span class="token function">Skip</span><span class="token punctuation">(</span>pageSize <span class="token operator">*</span> <span class="token punctuation">(</span>page <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Take</span><span class="token punctuation">(</span>pageSize<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>p <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductListItem</span><span class="token punctuation">(</span>p<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> p<span class="token punctuation">.</span>Sku<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> p<span class="token punctuation">.</span>Name<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToListAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">GetProducts</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Filter<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Page<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> PageSize <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">int</span></span> DefaultPage <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">int</span></span> DefaultPageSize <span class="token operator">=</span> <span class="token number">10</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">GetProducts</span> <span class="token function">With</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> filter<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> page<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span><span class="token punctuation">?</span></span> pageSize<span class="token punctuation">)</span> <span class="token punctuation">{</span> page <span class="token operator">??=</span> DefaultPage<span class="token punctuation">;</span> pageSize <span class="token operator">??=</span> DefaultPageSize<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>page <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>page<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>pageSize <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>pageSize<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token punctuation">(</span>filter<span class="token punctuation">,</span> page<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> pageSize<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductListItem</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Sku<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And <em>GetProductDetails</em> to return a single item with full set of properties:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">HandleGetProductDetails</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IQueryHandler<span class="token punctuation">&lt;</span>GetProductDetails<span class="token punctuation">,</span> ProductDetails<span class="token punctuation">?</span><span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IQueryable<span class="token punctuation">&lt;</span>Product<span class="token punctuation">></span></span> products<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">HandleGetProductDetails</span><span class="token punctuation">(</span><span class="token class-name">IQueryable<span class="token punctuation">&lt;</span>Product<span class="token punctuation">></span></span> products<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>products <span class="token operator">=</span> products<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>ProductDetails<span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">GetProductDetails</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> product <span class="token operator">=</span> <span class="token keyword">await</span> products <span class="token punctuation">.</span><span class="token function">SingleOrDefaultAsync</span><span class="token punctuation">(</span>p <span class="token operator">=></span> p<span class="token punctuation">.</span>Id <span class="token operator">==</span> query<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>product <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductDetails</span><span class="token punctuation">(</span> product<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> product<span class="token punctuation">.</span>Sku<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> product<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> product<span class="token punctuation">.</span>Description <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">GetProductDetails</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProductId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">GetProductDetails</span> <span class="token function">With</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> productId<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>productId<span class="token punctuation">.</span><span class="token function">AssertNotEmpty</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductDetails</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Sku<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Of course, eventually, we may need to optimise that. We can start by defining a custom (materialised) view or moving the endpoint to use different storage (e.g. Elastic Search for advanced search). As we have vertical slices, then this can be done easier.</p> <p>Minimal APIs are a good starting point for .NET to become lighter and have fewer ceremonies. Each abstraction brings additional cost. We should be trying to cut all the redundant layers to make our code composable and straightforward. CQRS can help with that by giving you the basic rules and skeleton for segregating your application behaviours. Are Minimal APIs and CQRS a perfect match? Nothing is perfect, but I think they’re good enough to at least play with it and consider it a building block in your architecture design.</p> <p>See the full sample code: <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/92/files">https://github.com/oskardudycz/EventSourcing.NetCore/pull/92/files</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. if you liked this article, then check also similar:</p> <ul> <li><a href="/en/cqrs_is_simpler_than_you_think_with_net6/">CQRS is simpler than you think with .NET 6 and C# 10</a></li> <li><a href="/en/how_to_build_simple_event_pipeline">How to build a simple event pipeline</a></li> </ul><![CDATA[Simple patterns for events schema versioning]]>https://event-driven.io/en/simple_events_versioning_patterns/https://event-driven.io/en/simple_events_versioning_patterns/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6d21a6ae40876678b22c9ed04ab514c7/d2429/2021-12-08-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABlklEQVQoz22Sz2oTURTGEzP3/Lt/Zu6d6cxgbNPEmpgi3QQLtTg2SqErKRoKfYK6ciOupJA3ELruK/gAbnwaH8NFmRmCadrLt7jcy4/zne+cDiIggkIABGqEayJCImwOPFSnJgFSw4Yxgvq+/t3tdqOofa71CIwII2+EMQjlmhWsLBDN9l8cHUyr4+poNgs+QVREBOswIzhryJhUKIsFCAiVgqjTw+svn85eTxK/VWSps1YpJcK5FV33sqpMhMxohGKvQdct7j3tj4eDatv/rPofpsX76t3xeDTJHCBsGfZC/+GGh0QoWHGW5uPh4s3s7eT550H/68TffrtanF8cDnde5oEbp/cqIwIAlJYPSusQD8tkvhM763ZDyJRafjy5Pj/ds3ZQZJkzFtHwBowQNE/LmEWPnJ5v52kSd3rq5Fn878+Pv7+WVRp7LfXwNtLGJl5GCNaGUIx88qoojUgvisZ58nt5efN9UcSGQbXkpu1WQqiFE6O1SDtYAGWfdDUBoGqjIQLNj8Gr/hXiaiUIkGljydpR3wFNwy1pg0CF+wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/6d21a6ae40876678b22c9ed04ab514c7/a331c/2021-12-08-cover.png" srcset="/static/6d21a6ae40876678b22c9ed04ab514c7/36ca5/2021-12-08-cover.png 200w, /static/6d21a6ae40876678b22c9ed04ab514c7/a3397/2021-12-08-cover.png 400w, /static/6d21a6ae40876678b22c9ed04ab514c7/a331c/2021-12-08-cover.png 800w, /static/6d21a6ae40876678b22c9ed04ab514c7/d2429/2021-12-08-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Events (schema) versioning is a boogeyman for people learning Event Sourcing. They’re a spooky tale told at the campfire. There’s a truth in it, as migrations are always challenging. As time flow, the events’ definition may change. Our business is changing, and we need to add more information. Sometimes we have to fix a bug or modify the definition for a better developer experience.</p> <p>Migrations are never easy, even in relational databases. You always have to think on:</p> <ul> <li>what caused the change?</li> <li>what are the possible solutions?</li> <li>is the change breaking?</li> <li>what to do with old data?</li> </ul> <p>We should always try to perform the change in a non-breaking manner. I explained that in <a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">Let’s take care of ourselves! Thoughts on compatibility</a> article.</p> <p>Still, the complexity and frequency of schema changes are usually overestimated. If you constantly need to change, that’s usually a sign of some modelling or communication issue with the business. Typically, business processes do not evolve rapidly. However, our understanding may often change if we’re learning while coding. It’s always worth ensuring that we’re doing enough thinking and design upfront.</p> <p>Also, as I wrote in <a href="/en/how_to_do_event_versioning/">How to (not) do the events versioning?</a>, it’s better to avoid such scenarios. How? By keeping our streams short-living:</p> <p><em>“If our aggregate lives shortly, a day or two, week, then these are easier to manage. If we’’‘re deploying new changes with the new events schema, then events with the old one will be living for at most a few days. Thanks to that, we can break our deployment into two phases. First, we deploy a version that supports both schemas and mark the old one as “obsolete”. Then, during the next deployment, we get rid of the code responsible for old events, because there are no living aggregate instances with old schemas.”</em></p> <p>Still, it’s always worth knowing those practices to use them depending on your use case. Greg Young wrote a book about it: <a href="https://leanpub.com/esversioning/read">Versioning in an Event Sourced System</a>. I recommend you to read it, especially that’s for free, which is always a reasonable price.</p> <p>I’ll try to explain today some of the basic strategies that will help you handle most of the basic scenarios.</p> <p>If you prefer watching than reading you can also check the video below:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/fDC465jJoDk?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <h2 id="simple-mapping" style="position:relative;"><a href="#simple-mapping" aria-label="simple mapping permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Simple mapping</h2> <p>There are some simple mappings that we could handle on the code structure or serialisation level. I’ll be showing samples using C# and <em>System.Text.Json</em> serialiser, but patterns and samples should be generic enough to apply to other environments and serialisers.</p> <h3 id="new-not-required-property" style="position:relative;"><a href="#new-not-required-property" aria-label="new not required property permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>New not required property</h3> <p>Having event defined as such:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>If we’d like to add a new not required property, e.g. <em>IntializedAt</em>, we can add it just as a new nullable property. The essential fact to decide if that’s the right strategy is if we’re good with not having it defined. It can be handled as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token comment">// Adding new not required property as nullable</span> <span class="token class-name">DateTime<span class="token punctuation">?</span></span> IntializedAt <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Then, most serialisers will put the null value by default and not fail unless we use strict mode. The new events will contain whole information, for the old ones we’ll have to live with that.</p> <h3 id="new-required-property" style="position:relative;"><a href="#new-required-property" aria-label="new required property permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>New required property</h3> <p>We must define a default value if we’d like to add a new required property and make it non-breaking. It’s the same as you’d add a new column to the relational table.</p> <p>For instance, we decide that we’d like to add a validation step when the shopping cart is open (e.g. for fraud or spam detection), and our shopping cart can be opened with a pending state. We could solve that by adding the new property with the status information and setting it to <em>Initialised</em>, assuming that all old events were appended using the older logic.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">ShoppingCartStatus</span> <span class="token punctuation">{</span> Pending <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> Opened <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">,</span> Confirmed <span class="token operator">=</span> <span class="token number">3</span><span class="token punctuation">,</span> Cancelled <span class="token operator">=</span> <span class="token number">4</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token comment">// Adding new required property with default value</span> <span class="token class-name">ShoppingCartStatus</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Opened <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <h3 id="renamed-property" style="position:relative;"><a href="#renamed-property" aria-label="renamed property permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Renamed property</h3> <p>Renaming property is also a breaking change. Still, we can do it in a non-breaking manner. We could keep the same name in the JSON but map it during (de) serialisation.</p> <p>Let’s assume that we concluded that keeping <em>ShoppingCart</em> prefix in the <em>ShoppingCartId</em> is redundant and decided to change it to <em>CartId</em>, as we see in the event name, what cart we have in mind.</p> <p>We could do it as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartOpened</span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">JsonPropertyName</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"ShoppingCartId"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> CartId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> cartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId <span class="token punctuation">)</span> <span class="token punctuation">{</span> CartId <span class="token operator">=</span> cartId<span class="token punctuation">;</span> ClientId <span class="token operator">=</span> clientId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>The benefit is that both old and the new structure will be backward and forward compatible. The downside of this solution is that we’re still keeping the old JSON structure, so all consumers need to be aware of that and do mapping if they want to use the new structure. Some serialisers like Newtonsoft Json.NET allows to do such magic:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartOpened</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> CartId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">init</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> cartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> clientId<span class="token punctuation">,</span> <span class="token class-name">Guid<span class="token punctuation">?</span></span> shoppingCartId <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> CartId <span class="token operator">=</span> cartId <span class="token operator">??</span> shoppingCartId<span class="token operator">!</span><span class="token punctuation">.</span>Value<span class="token punctuation">;</span> ClientId <span class="token operator">=</span> clientId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’ll either use the new property name or, if it’s not available, then an old one. The downside is that we had to pollute our code with additional fields and nullable markers. As always, pick your poison.</p> <h2 id="upcasting" style="position:relative;"><a href="#upcasting" aria-label="upcasting permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Upcasting</h2> <p>Sometimes we want to make more significant changes or be more flexible in the event mapping. We’d like to use a new structure in our code, not polluted by the custom mappings.</p> <p>We can use an upcasting pattern for that. We can plug a middleware between the deserialisation and application logic. Having that, we can either grab raw JSON or deserialised object of the old structure and transform it to the new schema.</p> <h3 id="changed-structure" style="position:relative;"><a href="#changed-structure" aria-label="changed structure permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Changed Structure</h3> <p>For instance, we decide to send also other information about the client, instead of just their id. We’d like to have a nested object instead of the flattened list of fields. We could model new event structure as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Client</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> Id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Name <span class="token operator">=</span> <span class="token string">"Unknown"</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Client</span> Client <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We can define upcaster as a function that’ll later plug in the deserialisation process.</p> <p>We can define the transformation of the object of the old structure as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartOpened</span> <span class="token function">Upcast</span><span class="token punctuation">(</span> <span class="token class-name">V1<span class="token punctuation">.</span>ShoppingCartOpened</span> oldEvent <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Client</span><span class="token punctuation">(</span>oldEvent<span class="token punctuation">.</span>ClientId<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Or we can map it from JSON</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartOpened</span> <span class="token function">Upcast</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> oldEventJson <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> oldEvent <span class="token operator">=</span> JsonDocument<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>oldEventJson<span class="token punctuation">)</span><span class="token punctuation">.</span>RootElement<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ShoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Client</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ClientId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h3 id="new-required-property-1" style="position:relative;"><a href="#new-required-property-1" aria-label="new required property 1 permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>New required property</h3> <p>We can also solve the same cases as simple mappings, but we have more handling options.</p> <p>Let’s say that we forget to add information about who initialised the shopping cart (user id). We cannot retroactively guess what the user was, but if we were lucky enough to track such information in user metadata (e.g. for tracing), we could try to map it.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">EventMetadata</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> UserId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> IntialisedBy <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Upcaster from old object to the new one can look like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartOpened</span> <span class="token function">Upcast</span><span class="token punctuation">(</span> <span class="token class-name">V1<span class="token punctuation">.</span>ShoppingCartOpened</span> oldEvent<span class="token punctuation">,</span> <span class="token class-name">EventMetadata</span> eventMetadata <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> oldEvent<span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> eventMetadata<span class="token punctuation">.</span>UserId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>From JSON to the object:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartOpened</span> <span class="token function">Upcast</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> oldEventJson<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> eventMetadataJson <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> oldEvent <span class="token operator">=</span> JsonDocument<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>oldEventJson<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> eventMetadata <span class="token operator">=</span> JsonDocument<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>eventMetadataJson<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> oldEvent<span class="token punctuation">.</span>RootElement<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ShoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> oldEvent<span class="token punctuation">.</span>RootElement<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ClientId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> eventMetadata<span class="token punctuation">.</span>RootElement<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"UserId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="downcasters" style="position:relative;"><a href="#downcasters" aria-label="downcasters permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Downcasters</h2> <p>In the same way, as described above, we can downcast the events from the new structure to the old one (if we have the old reader/listener or, for some reason, want to keep the old format).</p> <p>From the new object to the old one:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">V1<span class="token punctuation">.</span>ShoppingCartOpened</span> <span class="token function">Downcast</span><span class="token punctuation">(</span> <span class="token class-name">ShoppingCartOpened</span> newEvent <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V1<span class="token punctuation">.</span>ShoppingCartOpened</span><span class="token punctuation">(</span> newEvent<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> newEvent<span class="token punctuation">.</span>Client<span class="token punctuation">.</span>Id <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>From new JSON format to the old object:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">V1<span class="token punctuation">.</span>ShoppingCartOpened</span> <span class="token function">Downcast</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> newEventJson <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newEvent <span class="token operator">=</span> JsonDocument<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>newEventJson<span class="token punctuation">)</span><span class="token punctuation">.</span>RootElement<span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V1<span class="token punctuation">.</span>ShoppingCartOpened</span><span class="token punctuation">(</span> newEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ShoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> newEvent<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"Client"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"Id"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="events-transformations" style="position:relative;"><a href="#events-transformations" aria-label="events transformations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Events Transformations</h2> <p>At this point, you may be wondering, “That’s nice, but how to connect that with real code?“. Let’s dive into that.</p> <p>We’ll be plugging between the serialisation and application logic as explained initially. We’ll define the class that will contain and process all defined transformations.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventTransformations</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> Func<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span><span class="token punctuation">></span></span> jsonTransformations <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> <span class="token function">TryTransform</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> json<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">?</span></span> result<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>jsonTransformations<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> transformJson<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> result <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> result <span class="token operator">=</span> <span class="token function">transformJson</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token boolean">true</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">EventTransformations</span> <span class="token generic-method"><span class="token function">Register</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>JsonDocument<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span> transformJson<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TEvent</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token punctuation">{</span> jsonTransformations<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> eventTypeName<span class="token punctuation">,</span> json <span class="token operator">=></span> <span class="token function">transformJson</span><span class="token punctuation">(</span>JsonDocument<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">EventTransformations</span> <span class="token generic-method"><span class="token function">Register</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TOldEvent<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TOldEvent<span class="token punctuation">,</span> TEvent<span class="token punctuation">></span></span> transformEvent<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TOldEvent</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token keyword">where</span> <span class="token class-name">TEvent</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">notnull</span></span> <span class="token punctuation">{</span> jsonTransformations<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span> eventTypeName<span class="token punctuation">,</span> json <span class="token operator">=></span> <span class="token function">transformEvent</span><span class="token punctuation">(</span>JsonSerializer<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Deserialize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TOldEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>json<span class="token punctuation">)</span><span class="token operator">!</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We have two <em>Register</em> methods. Both of them has JSON and handler function as params. One is used to register the <em>JsonDocument</em> raw transformation, the other to register an object to object one. Sample registrations:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> transformations <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventTransformations</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Register</span><span class="token punctuation">(</span>eventTypeV1Name<span class="token punctuation">,</span> UpcastV1<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Register</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartOpened<span class="token punctuation">,</span> ShoppingCartIntialisedWithStatus<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> eventTypeV2Name<span class="token punctuation">,</span> UpcastV2<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We also have <em>TryTransform</em> that either transforms JSON into the new object structure or returns <em>null</em>. We’ll use it further on.</p> <p>Let’s also define the type mapping class responsible for mapping event type name into the CLR type.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventTypeMapping</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Dictionary<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">,</span> Type<span class="token punctuation">></span></span> mappings <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">EventTypeMapping</span> <span class="token generic-method"><span class="token function">Register</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">params</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> typeNames<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> eventType <span class="token operator">=</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">TEvent</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> typeName <span class="token keyword">in</span> typeNames<span class="token punctuation">)</span> <span class="token punctuation">{</span> mappings<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>typeName<span class="token punctuation">,</span> eventType<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Type</span> <span class="token function">Map</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventType<span class="token punctuation">)</span> <span class="token operator">=></span> mappings<span class="token punctuation">[</span>eventType<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>and use it as</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> eventTypeV1Name <span class="token operator">=</span> <span class="token string">"shopping_cart_initialised_v1"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> eventTypeV2Name <span class="token operator">=</span> <span class="token string">"shopping_cart_initialised_v2"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token class-name"><span class="token keyword">string</span></span> eventTypeV3Name <span class="token operator">=</span> <span class="token string">"shopping_cart_initialised_v3"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> mapping <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventTypeMapping</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Register</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartIntialisedWithStatus<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> eventTypeV1Name<span class="token punctuation">,</span> eventTypeV2Name<span class="token punctuation">,</span> eventTypeV3Name <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>It’s the most straightforward wrapper that requires manual mapping for all the event types, but it benefits from being explicit and less error-prone. For the convention-based mapper, there is a risk that refactoring accidentally changes the event type name stored in the event store. Still, a viable option is a mixed solution. Read also more in <a href="/en/how_to_map_event_type_by_convention/">Mapping event type by convention</a> or see example of such approch in <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Core/Events/EventTypeMapper.cs">my repository</a>.</p> <p>Having those classes, we can define the final deserialisation logic.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventSerializer</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">EventTypeMapping</span> mapping<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">EventTransformations</span> transformations<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">EventSerializer</span><span class="token punctuation">(</span><span class="token class-name">EventTypeMapping</span> mapping<span class="token punctuation">,</span> <span class="token class-name">EventTransformations</span> transformations<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mapping <span class="token operator">=</span> mapping<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>transformations <span class="token operator">=</span> transformations<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">?</span></span> <span class="token function">Deserialize</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> json<span class="token punctuation">)</span> <span class="token operator">=></span> transformations<span class="token punctuation">.</span>TryTransform<span class="token class-name"><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> json<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> transformed<span class="token punctuation">)</span> <span class="token punctuation">?</span></span> transformed <span class="token punctuation">:</span> JsonSerializer<span class="token punctuation">.</span><span class="token function">Deserialize</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> mapping<span class="token punctuation">.</span><span class="token function">Map</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>The logic is simple. It’ll either transform JSON through registered transformations (e.g. upcasters or downcasters) or run the regular deserialisation logic.</p> <h2 id="stream-transformation" style="position:relative;"><a href="#stream-transformation" aria-label="stream transformation permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Stream Transformation</h2> <p>You might want not only to transform a single event into another (1:1) but also a set of events into another one (N:M).</p> <p>Let’s take as an example scenario where we can initialise not only empty shopping cart but also filled with products. For some time, we were doing that by publishing multiple events: <em>ShoppingCartIntialised</em> and <em>ProductItemAddedToShoppingCart</em> for each added product item. We decided that we’d like to replace this with event containing list of product items:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItem</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ProductId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">int</span></span> Quantity <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">PricedProductItem</span><span class="token punctuation">(</span> <span class="token class-name">ProductItem</span> ProductItem<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">decimal</span></span> UnitPrice <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartOpened</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartIntialisedWithProducts</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>V1<span class="token punctuation">.</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We want to process our logic using a new event schema. However, that’d require zipping multiple stream events into a single one. We were lucky enough that we decided to store in metadata correlation id. It’s an identifier of the command that initiated business logic. All of the events resulting from the command processing will share the same correlation id.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3bed2de5dde0044cbb6f941ad1a296c6/0a63b/2021-12-08-streams.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 47%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAJCAIAAAC9o5sfAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABw0lEQVQozy3Qy5KTQACFYd7/MbSmdGFZmprI6GSGYCBXQhIkEEIT7nTTV5qLGXVjZbTqX5zNtzkKlwOiDZdX9etkdP/lWTc03Xh79+7Dx9HnkbqxnbH67c3de+27OVYfJ9pM000mOt7+JLxXatoi2iLWOh5Y2858tZ2vd+OHJ/VR+3T/MNEMc2k96+Zis9ONlbmyn6YmJJKJ/oYhlmeQ+gEAcQaxiOLM9YISEog5uKQgSuMMegGw7EOSw5rJMEoc9xSnFRO9QkWfRW7grP3DhqAUuJZjGUUaFrHvbk1vv6J1HrrW0Z5nl1OZnv3D0t8vaoQw6xXMO0I5ZSKM4tM5KkqYF1UYJbId4jSrEOZNDxG+xAnjkrImyXJECBUdYZ3C5ZXJK2+von9BrI1SeL4Urg8c7/zDCwnv5fAb0uYEEjn8afpfSVlHadV0LzeMWUdER/hroidiQFSWNZ9MzelsEaUVJDKMywBklwyWNQdJ6fpRktc3fLuatJDImrY1azHvMO9oM1RJsNRVZzNjpAJHy7MN4O+rPPJ3i+N2VpU5EYOCeY/Zf8Be+zdqjOMkLkvIeFNVMMvyCiKMaZEXRVEQJgjv/gIdz9Y08GpLAQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="stream transformation" title="stream transformation" src="/static/3bed2de5dde0044cbb6f941ad1a296c6/a331c/2021-12-08-streams.png" srcset="/static/3bed2de5dde0044cbb6f941ad1a296c6/36ca5/2021-12-08-streams.png 200w, /static/3bed2de5dde0044cbb6f941ad1a296c6/a3397/2021-12-08-streams.png 400w, /static/3bed2de5dde0044cbb6f941ad1a296c6/a331c/2021-12-08-streams.png 800w, /static/3bed2de5dde0044cbb6f941ad1a296c6/8537d/2021-12-08-streams.png 1200w, /static/3bed2de5dde0044cbb6f941ad1a296c6/0a63b/2021-12-08-streams.png 1331w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Using it, we could decide if <em>ProductItemAddedToShoppingCart</em> was a part of the initialisation request or not.</p> <p>We need to take the stream events and transform them into another sequence of events. It could be modeled by such class:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">StreamTransformations</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>Func<span class="token punctuation">&lt;</span>List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span><span class="token punctuation">,</span> List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span><span class="token punctuation">></span><span class="token punctuation">></span></span> jsonTransformations <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span></span> <span class="token function">Transform</span><span class="token punctuation">(</span><span class="token class-name">List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span></span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>jsonTransformations<span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">return</span> events<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> jsonTransformations <span class="token punctuation">.</span><span class="token function">Aggregate</span><span class="token punctuation">(</span>events<span class="token punctuation">,</span> <span class="token punctuation">(</span>current<span class="token punctuation">,</span> transform<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">transform</span><span class="token punctuation">(</span>current<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">StreamTransformations</span> <span class="token function">Register</span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span><span class="token punctuation">,</span> List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span><span class="token punctuation">></span></span> transformJson <span class="token punctuation">)</span> <span class="token punctuation">{</span> jsonTransformations<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>transformJson<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We allow registering multiple transformations. Thanks to that, we can chain them using the <em>Aggregate</em> method, taking the previous transformation’s result as a base for the next one.</p> <p>To connect it with the deserialisation process, we need to add it to the <em>EventSerializer</em> defined in the previous steps.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EventSerializer</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">EventTypeMapping</span> mapping<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">StreamTransformations</span> streamTransformations<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">EventTransformations</span> transformations<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">EventSerializer</span><span class="token punctuation">(</span><span class="token class-name">EventTypeMapping</span> mapping<span class="token punctuation">,</span> <span class="token class-name">StreamTransformations</span> streamTransformations<span class="token punctuation">,</span> <span class="token class-name">EventTransformations</span> transformations<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>mapping <span class="token operator">=</span> mapping<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>transformations <span class="token operator">=</span> transformations<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>streamTransformations <span class="token operator">=</span> streamTransformations<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">?</span></span> <span class="token function">Deserialize</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> eventTypeName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> json<span class="token punctuation">)</span> <span class="token operator">=></span> transformations<span class="token punctuation">.</span>TryTransform<span class="token class-name"><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">,</span> json<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> transformed<span class="token punctuation">)</span> <span class="token punctuation">?</span></span> transformed <span class="token punctuation">:</span> JsonSerializer<span class="token punctuation">.</span><span class="token function">Deserialize</span><span class="token punctuation">(</span>json<span class="token punctuation">,</span> mapping<span class="token punctuation">.</span><span class="token function">Map</span><span class="token punctuation">(</span>eventTypeName<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name">List<span class="token punctuation">&lt;</span><span class="token keyword">object</span><span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token function">Deserialize</span><span class="token punctuation">(</span><span class="token class-name">List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span></span> events<span class="token punctuation">)</span> <span class="token operator">=></span> streamTransformations<span class="token punctuation">.</span><span class="token function">Transform</span><span class="token punctuation">(</span>events<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>@<span class="token keyword">event</span> <span class="token operator">=></span> <span class="token function">Deserialize</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>EventType<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Data<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re injecting stream transformations into the deserialisation process. We’re performing them first before running upcasters or regular deserialisation.</p> <p>We can implement function doing event grouping as described above as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span></span> <span class="token function">FlattenIntialisedEventsWithProductItemsAdded</span><span class="token punctuation">(</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span></span> events <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> cartIntialised <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token function">First</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> cartIntialisedCorrelationId <span class="token operator">=</span> JsonSerializer<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Deserialize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>EventMetadata<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>cartIntialised<span class="token punctuation">.</span>MetaData<span class="token punctuation">)</span><span class="token operator">!</span> <span class="token punctuation">.</span>CorrelationId<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> i <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span></span> productItemsAdded <span class="token operator">=</span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">while</span> <span class="token punctuation">(</span>i <span class="token operator">&lt;</span> events<span class="token punctuation">.</span>Count<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> eventData <span class="token operator">=</span> events<span class="token punctuation">[</span>i<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>eventData<span class="token punctuation">.</span>EventType <span class="token operator">!=</span> <span class="token string">"product_item_added_v1"</span><span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> correlationId <span class="token operator">=</span> JsonSerializer <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Deserialize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>EventMetadata<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>eventData<span class="token punctuation">.</span>MetaData<span class="token punctuation">)</span><span class="token operator">!</span> <span class="token punctuation">.</span>CorrelationId<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>correlationId <span class="token operator">!=</span> cartIntialisedCorrelationId<span class="token punctuation">)</span> <span class="token keyword">break</span><span class="token punctuation">;</span> productItemsAdded<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>eventData<span class="token punctuation">)</span><span class="token punctuation">;</span> i<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token class-name"><span class="token keyword">var</span></span> mergedEvent <span class="token operator">=</span> <span class="token function">ToShoppingCartIntialisedWithProducts</span><span class="token punctuation">(</span> cartIntialised<span class="token punctuation">,</span> productItemsAdded <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span></span><span class="token punctuation">(</span> <span class="token keyword">new</span><span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> mergedEvent <span class="token punctuation">}</span><span class="token punctuation">.</span><span class="token function">Union</span><span class="token punctuation">(</span>events<span class="token punctuation">.</span><span class="token function">Skip</span><span class="token punctuation">(</span>i<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">EventData</span> <span class="token function">ToShoppingCartIntialisedWithProducts</span><span class="token punctuation">(</span> <span class="token class-name">EventData</span> shoppingCartIntialised<span class="token punctuation">,</span> <span class="token class-name">List<span class="token punctuation">&lt;</span>EventData<span class="token punctuation">></span></span> productItemsAdded <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> shoppingCartIntialisedJson <span class="token operator">=</span> JsonDocument<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>shoppingCartIntialised<span class="token operator">!</span><span class="token punctuation">.</span>Data<span class="token punctuation">)</span><span class="token punctuation">.</span>RootElement<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> newEvent <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartIntialisedWithProducts</span><span class="token punctuation">(</span> shoppingCartIntialisedJson<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ShoppingCartId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> shoppingCartIntialisedJson<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ClientId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>V1<span class="token punctuation">.</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span> productItemsAdded<span class="token punctuation">.</span><span class="token function">Select</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> pricedProductItem <span class="token operator">=</span> JsonDocument<span class="token punctuation">.</span><span class="token function">Parse</span><span class="token punctuation">(</span>pi<span class="token punctuation">.</span>Data<span class="token punctuation">)</span><span class="token punctuation">.</span>RootElement<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ProductItem"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> pricedProductItem<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ProductItem"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V1<span class="token punctuation">.</span>PricedProductItem</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">V1<span class="token punctuation">.</span>ProductItem</span><span class="token punctuation">(</span>productItem<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"ProductId"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> productItem<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"Quantity"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetInt32</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">,</span> pricedProductItem<span class="token punctuation">.</span><span class="token function">GetProperty</span><span class="token punctuation">(</span><span class="token string">"UnitPrice"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">GetDecimal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">EventData</span><span class="token punctuation">(</span><span class="token string">"shopping_cart_initialised_v2"</span><span class="token punctuation">,</span> JsonSerializer<span class="token punctuation">.</span><span class="token function">Serialize</span><span class="token punctuation">(</span>newEvent<span class="token punctuation">)</span><span class="token punctuation">,</span> shoppingCartIntialised<span class="token punctuation">.</span>MetaData<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <h2 id="migrations" style="position:relative;"><a href="#migrations" aria-label="migrations permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Migrations</h2> <p>You can say that, well, those patterns are not migrations. Events will stay as they were, and you’ll have to keep the old structure forever. That’s quite true. Still, this is fine, as typically, you should not change the past. Having precise information, even including bugs, is a valid scenario. It allows you to get insights and see the precise history. However, pragmatically you may sometimes want to have a “clean” event log with only a new schema.</p> <p>It appears that composing the patterns described above can support such a case. For example, if you’re running EventStoreDB or Marten, you can read/subscribe to the event stream, store events in the new stream, or even a new EventStoreDB cluster or Postgres schema. Having that, you could even rewrite the whole log and switch databases once the new one caught up.</p> <p>I hope that those samples will show you that you can support many versioning scenarios with basic composition techniques.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3ac1bb16ece9486a488ee5f2afd355a2/bc911/2021-12-08-migration.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 51.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAYAAAC0VX7mAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAB2ElEQVQoz02SW0/bQBCF/f//RaVKfWipItoIAkENBNK4QKgqUFoiCMrN3ovHl00Cfai+atcO4uFodsZnjs7MOJJyixEXMHmY8WmvRX8Q0+sP+NI+5ONei/bBMcMfIwbDK969/8AgvqJz/I3e2fdQM1Ii5QadOaIs35DaMiRPC8UgHnF2EdO/iDk6OaP1tcPn/QP220dcDK9pH3Y5H1xxdNLn9Dym0+2RmIIsX2NkTbTSBamtgqDJ10j1Qu5eWFmLkoqyKHBljmQGnSaIWGapwhRr3PO/wE1MSaoLdFYRaamFAsRh8w2T2RM39z+5m/4mWdyj52PUfIyZj1mtJlyXl0xlhsk2aKnQuUOFKYPgOrjzUF6w2PIwWzIcXTO6vSVRGmVzEm1RJiPNhMv0kqlZYmUbeoKZRiOS6hl/mKzcIFUds8LHF0TPSaY3GKPI3V9yvw6Pou7x/Lpni5VG0O9PZVWwG1x6hJojVQY1u0M9/UJrIbUucHfw66rfDuX3aCsv6MIjbQphfKn3qWWD0obl6pFFoUhsEb4FfoOdsGkMRapxFcTeHMiG6P/PLY/ZglPTZSUS8h3/VczzdyOHk+9gy1enNXxeMTeasfrD0gjKOpI3/KThK1OE/D8ZfuGsytFwZAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="migrations" title="migrations" src="/static/3ac1bb16ece9486a488ee5f2afd355a2/a331c/2021-12-08-migration.png" srcset="/static/3ac1bb16ece9486a488ee5f2afd355a2/36ca5/2021-12-08-migration.png 200w, /static/3ac1bb16ece9486a488ee5f2afd355a2/a3397/2021-12-08-migration.png 400w, /static/3ac1bb16ece9486a488ee5f2afd355a2/a331c/2021-12-08-migration.png 800w, /static/3ac1bb16ece9486a488ee5f2afd355a2/8537d/2021-12-08-migration.png 1200w, /static/3ac1bb16ece9486a488ee5f2afd355a2/bc911/2021-12-08-migration.png 1307w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Nevertheless, the best approach is to <a href="/en/how_to_do_event_versioning/">not need to do versioning at all</a>. If you’re facing such a need, before using the strategies described above, make sure that your business scenario cannot be solved by talking to the business. It may appear that’s some flaw in the business process modelling. We should not be trying to fix the issue, but the root cause.</p> <p><strong>You can check full sample in:</strong></p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/EventsVersioning">C#</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.JVM/tree/main/samples/events-versioning">Java</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.NodeJS/tree/main/samples/eventsVersioning">Typescript</a>.</li> </ul> <p><strong>Watch also more in the webinar:</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAAAAAAD6AF+hNEZAAABbUlEQVQoz4WS3W4TMRCF/dqgvgGXcM1LcIOQoKpIN222m+xmf/0zc2bGacljoN1NqFQhOFdj2eez54ydzhJV+asgWIv5gAqAl1/n+7tvnz9+uHn33gGXbb5W4LkEQEQgUXkDhlk+Pb+cz2dHDFUl5hQ8JYoxiZ0guvoPfpc4/fEvdADMzETkMHvVBNUQ98cdp1qOW429aLZsZVOEGPTaVVYxy2KndelCUIiYcOm1OP7s/WZT3Lb1NiuO1bG+78GX5w7DcPDU+Ya7Hc2dsYtJzYyYnzX3sSm7otrdteWPafOlejwQpTUKAPMFMVf1Lb5+Ejsxs/OjNzVeQlI1FZvaMozN5NNTUcYp4NKYdvsRgbNIGvYkM9TJEsYaD2NBWBY1VfHjVBfd0Pbd4/dpGg5FP/VhpoS2bAuBudcJvIqxUpYvACCEwMzRpxSJiIL3KZGqOL7O+R+6pL38k3VsTVP3Q3TM/ze/QTHjYVuUT+NvFbc119IGMYMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png" srcset="/static/5884d457fe2181ab0e490a1460ab913c/36ca5/2021-12-08-webinaresver.png 200w, /static/5884d457fe2181ab0e490a1460ab913c/a3397/2021-12-08-webinaresver.png 400w, /static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png 800w, /static/5884d457fe2181ab0e490a1460ab913c/8537d/2021-12-08-webinaresver.png 1200w, /static/5884d457fe2181ab0e490a1460ab913c/1a152/2021-12-08-webinaresver.png 1600w, /static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>And read in the versioning series:</strong></p> <ul> <li><a href="/en/how_to_do_event_versioning/">How to (not) do the events versioning?</a></li> <li><a href="/en/explicit_events_serialisation_in_event_sourcing/">Explicit events serialisation in Event Sourcing</a></li> <li><a href="/en/fun_with_json_serialisation/">Fun with serial JSON</a></li> <li><a href="/en/how_to_map_event_type_by_convention/">Mapping event type by convention</a></li> <li><a href="/en/event_versioning_with_marten/">Event Versioning with Marten</a></li> <li><a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">Let’s take care of ourselves! Thoughts on compatibility</a></li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a></li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[Event Streaming is not Event Sourcing!]]>https://event-driven.io/en/event_streaming_is_not_event_sourcing/https://event-driven.io/en/event_streaming_is_not_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/56f85bf712e80a68e0b161404a4f9717/d2429/2021-12-01-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA6/AAAOvwE4BVMkAAADKElEQVQozwXB7VMaBBzAcd70pve2dfPmphNHqICI4ERUVAREUR4ERERQwYccmqapw1SyWbq5tvRahi6WrmXa5erqlqu7uXldu+66XvSml/0fv2+fj6LVVY+5Vk+VUYXPX8/wYDtjUSd3F+L0dNkovHSBpek+1tJx6mrU1OmLaDAW09JUTptTj61Ri62+kiazjsoKNYo2pxFlST4Fl89TW/cWbnsFvYEmwjYD86Ne1KWFlKkKqdJeoVRVgNmoorlRh8mgosGio9New52pIEvDneS9kYeiq70au02HpVpNzGNhLd1NNFSLp8mEv6WFYnUpZZoCDEYllcYyTDUG+gNWUlGnfDITkdkBN5NRh7RaytFevYgiPeZnfjzESJ+DerMSvTaf8uIL5JeHqaxo5XxRM5dVZTRYa4jEYqLRaSXU5ZaR4UEm4x2MBqwSdlUTdpkI2A0ohnodtLboKbpyjuKSN6kx5NNs03GuqJrXXs+XEo0Nl7tbJmdmSU1MSE80LrncIzl78Uqy64tyM+VjfToiK9d9zCVcKMwGJQWX8rhmKsTXqcXrLycY1vB22Iyy8CKdfp/cz+6wcnND5maXyO0+4Ojx1+zlHvJg845szoRlN5NgK5OUz1eSKJprNViqiuhwqAl6NXg61Cwk7Px4a4hAq0niAyOcnv4lv528ZGd7Vx7v7cn3h0fy+4vn8uTgSDbeDcunC318sZKU/bVhFHXVJTgbVfjcpUR9enraK7g14Sf3wYDMJpxk5t6Rk4eH8uybJ7z64285O/1TTn5+Lj/98KscHhyTSgbl3mxUvl0d4vCjBIpQm4Z4oJJQuxZXrQpfo4bbU0HWp0Myn2zH01wh+/d35d9//pNnT19y/N2JHDw6luzWJgupqNwY8rKxlGQzHZPtxRiKuFdH0FGGx6omaNMw3WclOxdi9bqHdMIpQYeOualx+eXpmexufyWZ9AKTg118PO6W7GKEXKZfttK93L3Ry2fzURSd9Upclqt02bWMREysjjrYnOlmdaxDPhx1MhGxitdxjVQsyPKIl8xYm9x5LyzZ93vJLUdlJ9PH7fkoXy7H2F/p53+p5dOGTPNYSwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/56f85bf712e80a68e0b161404a4f9717/a331c/2021-12-01-cover.png" srcset="/static/56f85bf712e80a68e0b161404a4f9717/36ca5/2021-12-01-cover.png 200w, /static/56f85bf712e80a68e0b161404a4f9717/a3397/2021-12-01-cover.png 400w, /static/56f85bf712e80a68e0b161404a4f9717/a331c/2021-12-01-cover.png 800w, /static/56f85bf712e80a68e0b161404a4f9717/d2429/2021-12-01-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I usually don’t try to fight the HackerNews or Reddit opinions. That said, I cannot deny that they’re powerful platforms. The heated rants can create a significant impact on the community. Rants around Event Sourcing are appear from time to time. The leitmotiv is <a href="https://chriskiehl.com/article/event-sourcing-is-hard">“Don’t Let the Internet Dupe You, Event Sourcing is Hard”</a> article.</p> <p>As this article popped out again <a href="https://news.ycombinator.com/item?id=29390483">here</a> and <a href="https://www.reddit.com/r/programming/comments/r5p8qj/dont_let_the_internet_dupe_you_event_sourcing_is/">there</a>, I decided to step up this time.</p> <p>This article has some valid points, but those points are not about Event Sourcing. What’s expressed in the article is not Event Sourcing. It describes Event Streaming or, in general, Event-Driven design issues.</p> <p><strong>The main misunderstanding, and source of the issues, is related to deciding on the stale data</strong>. It brings uncertainty and the need for workarounds. It’s a common mistake to use tools like Kafka and Pulsar for event stores, but they are not. You cannot read the stream events but subscribe. You don’t have basic guarantees for optimistic concurrency checks. I wrote about how important this is in:</p> <ul> <li><a href="/en/optimistic_concurrency_for_pessimistic_times/">Optimistic concurrency for pessimistic times</a></li> <li><a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">How to use ETag header for optimistic concurrency</a></li> </ul> <p>TLDR: We assume that conflict situations will be rare. A conflict arises when two people try to change the same record at the same time. When this happens, we will only allow the first person to update the state. All other updates will be rejected. For verification, we use a record version that changes with each save.</p> <p>Have a look at this ticket: <a href="https://issues.apache.org/jira/browse/KAFKA-2260">https://issues.apache.org/jira/browse/KAFKA-2260</a>. The minor priority states clearly, that’s not the typical use case for Kafka.</p> <p>All of the event stores that I know support strong consistency on appends, and optimistic concurrency. Most guarantee global ordering. Some have a built-in idempotency handling. All of that helps to reduce those issues. <strong>In Event Sourcing, events are the state.</strong> So the flow looks like this:</p> <ol> <li>You read the events from the stream (that represents all the facts that happened for the entity),</li> <li>You apply it one by one in the order of appearance to build the current state,</li> <li>You run the business logic and, as a result, create the event,</li> <li>You append a new event.</li> </ol> <p>You don’t need to cache the write model state anywhere, as the state is in events. Event Sourcing has its issues and troubles, but the stuff described in the article applies to tools like Kafka, etc. They’re not tools for Event Sourcing, but Event Streaming. They’re designed to move things from one place to another, not to be used as durable databases. Of course, they have durable storage capabilities. However, they were not added initially to be used as databases. These features were added to make them fault-tolerant and resilient. Thanks to that, they can support scenarios where brokers went down, or consumers are not available. They also have an append-only log inside and are designed to be the central point of the event-driven design. However, none of that makes events a source of truth. At least not in the Event Sourcing meaning.</p> <p><strong>The events are the source of truth, only if you’re using them in the write model as a basis for the state rehydration.</strong> If you’re using a materialized view, even though it’s built based on events, then you outsourced the truth to other storage. If you’re using a pattern that means you’re just using events to build up the materialized view you use for the write model logic, that can lead to using Event Streaming tools like Kafka, Pulsar, etc. And, as stated in the article, this is a dead-end. Read more in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a>.</p> <p>Of course, rebuilding a state from events each time you’re processing command may sound dubious. Still, event stores (even those with relational DBs as backing storage) are optimized to read events quickly. Typically, reading 100 events is not an issue for them. It also shouldn’t be a performance issue for a typical business application. The temptation to use Snapshots is strong, but it should be treated as an optimization when there are huge performance requirements. They may get out of sync with events, they may be stale, etc. I wrote about this in: <a href="https://www.eventstore.com/blog/snapshots-in-event-sourcing">Snapshots in Event Sourcing</a>.</p> <p><strong>The critical part is to keep streams short.</strong> That’s again a difference between Event Streaming and Event Sourcing. In Kafka, you may not care how long your topic is, as it’s just a pipe. Event stores are databases, so the more events you have, the worse. It impacts not only performance, but also makes schema versioning harder (more on that in <a href="/en/how_to_do_event_versioning/">How to (not) do the events versioning?</a>).</p> <p>It’s easy to fall into the trap, as using a streaming solution is tempting. They promise a lot, but eventually, you may end up having issues, as described in the article. Still, that can be said on any technology.</p> <p>Event Sourcing has some tricky parts. It’s essential to highlight them, but it’s dangerous to present problems from other patterns and tools as problems that have occurred due to Event Sourcing. I wrote it e.g.:</p> <ul> <li><a href="/en/when_not_to_use_event_sourcing/">When not to use Event Sourcing?</a></li> <li><a href="/en/audit_log_event_sourcing/">Is the audit log a proper architecture driver for Event Sourcing?</a></li> <li><a href="/en/property-sourcing/">Anti-patterns in event modelling - Property Sourcing</a></li> <li><a href="/en/state-obsession/">Anti-patterns in event modelling - State Obsession</a></li> <li><a href="/en/i_will_just_add_one_more_field/">Anti-patterns in event modelling - I’ll just add one more field</a>.</li> </ul> <p>You can also watch my talk about The Light and The Dark Side of the Event-Driven Design:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/0pYmuk0-N_4?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>Event Sourcing by itself doesn’t directly relate to eventual consistency, type of storage, messaging, etc. Those are implementation details and tradeoffs we’re choosing. Each storage solution has its patterns and anti-patterns: relational databases have normalization, document databases are denormalized, key-value stores have strategies for key definition. Event stores also have their problems. The most important aspect is (as I mentioned) to take into account the temporal aspect of streams, which means keeping them short. I agree that there is a huge gap in knowledge sharing. That’s why I wrote the extensive article on modelling aspect: <a href="https://www.eventstore.com/blog/keep-your-streams-short-temporal-modelling-for-fast-reads-and-optimal-data-retention">Keep your streams short! Temporal modeling for fast reads and optimal data retention</a></p> <p>The author of the original article confused Event Sourcing and talks about Event Streaming. <strong>Event Streaming means:</strong></p> <ol> <li>Producer pushes/produces/publishes an event to the queue. In this sense, an event stream is a pipe where you put an event at the end and receive it on the other side.</li> <li>Then, you have a set of subscribers that can react to those events. The reaction may be triggering the next step of the workflow. It might also be updating the read model state. That happens with eventual consistency. You also cannot guarantee atomicity or optimistic concurrency, as tools such as Kafka don’t support it. They have not been built for it, but they let you effectively publish and subscribe to the messages.</li> <li>Therefore, if you want to have the write model materialized from an event (or even “populated”), then you don’t have any guarantee if it’s stale or not. You need to fight with out of order messages and idempotency. Thus you have all of those issues that are described in the article.</li> </ol> <p><strong>Event Sourcing and Event Streaming are different patterns that happen to integrate with each other.</strong> Event Sourcing is about durable state stored and read as events, and Event Streaming is about moving events from one place to another.</p> <p>Event stores can integrate with streaming platforms, to publish events and move them forward. Most of them have even built-in implementation of the Outbox Pattern that allows publishing events. They’re usually called subscriptions. They can be used to build read models or trigger other flows, or route events to streaming platforms.</p> <p>The essential point is that for read models, you don’t expect to have the same guarantees as for write models. It’s fine to be facing idempotency, even out of order, as read models shouldn’t be used (in general) for business logic validation. In Event Streaming, the write models is stale. In Event Sourcing, the write model is not stale. You can be sure that you’re making a decision on the current state. Thus, most of the points mentioned in the article, are just from struggles to use event streaming solutions as tools for storing events. This is not the issue of Event Sourcing or event stores per se, but using the wrong tool for the job.</p> <p><strong>I’m sorry to say, but the author used the wrong tool for the wrong job.</strong> If this article was titled <em>“Event Sourcing is hard if you’re confusing it with Event Streaming”</em>, then it’d be an excellent article. But, in its current shape, it’s just misleading and making the wrong point, repeating just common misunderstandings. It’s highly misleading for people who are not aware of what Event Sourcing is and may suggest that it’s much more complicated than it’s in reality.</p> <p>I also covered that in my talk:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/20zvAJAhqS0?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong>I’ll repeat it again: this is not Event Sourcing.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s.</p> <p>If you liked this article, also check others where I’m trying to bust similar myths:</p> <ul> <li><a href="/en/dont_let_event_driven_architecture_buzzwords_fool_you/">Don’t let Event-Driven Architecture buzzwords fool you</a>,</li> <li><a href="/en/cqrs_facts_and_myths_explained/">CQRS facts and myths explained</a>.</li> </ul> <p>You can read more in:</p> <ul> <li><a href="https://domaincentric.net/blog/eventstoredb-vs-kafka">EventStoreDB vs Kafka written by Kacper Gunia</a>,</li> <li><a href="https://medium.com/itnext/event-sourcing-why-kafka-is-not-suitable-as-an-event-store-796e5d9ab63c">Event Sourcing: Why Kafka is not suitable as an Event Store by Anton Stöckl</a>,</li> <li><a href="https://www.eventstore.com/event-sourcing">A Beginner’s Guide to Event Sourcing from Event Store team</a>.</li> </ul> <p>Check Derek’s Comartin video explaining his concerns around this topic: <a href="https://www.youtube.com/watch?v=Y7ca1--EKsg">Don’t Let the Internet Dupe You, that’s NOT Event Sourcing</a>.</p> <p>I’ve also made a list of articles with wrong perspective on Event Sourcing. Read it <a href="https://github.com/oskardudycz/EventSourcing.NetCore#this-is-not-event-sourcing">here</a></p><![CDATA[How to register all CQRS handlers by convention]]>https://event-driven.io/en/how_to_register_all_mediatr_handlers_by_convention/https://event-driven.io/en/how_to_register_all_mediatr_handlers_by_convention/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/1c8b4d82412ce0658d13380d547a00fd/d2429/2021-11-24-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACeUlEQVQozy2SPUwTYQCGb3dQm/589/3cz9dr72uvZ++uvfbaQnstIEhbWqAhJQWKqBBoiDQG40BSbTop6mD8SWQxMToYJ2NMdDNhEQcT3TQOjkxOOmJQ3+Gdnjd5hpejVMAEQwgB4OG/oL+FMYDQ4wsEAC9SomoikYjPDyAPeZ5HGEGEuJAiIIIwRmpIxhj9nyMIEFIl3MppeT0IMCQS0U3q5pgsYyoQ/phBXCwkBCkBANxYn3bThtfnRwhhhE75wUJO/dKf6c843gBQQmSpZm4t2+3ZpGsxbwBgAXM6E5kqUBm+GLTXGiMnTnowQgJGpwE/n1b2u8W95XxA5AtZ5cpCYmfVGWxM6jr1g2Nzzo7LtkktQ3p9//KbB9sZWwcQIoJ8CBYToR9Pev2NFpG9TkJqTEQ3m4nyiHouz2JhGUCeS5vUsoJRTXh5p3Pw7Pr02WTSCObTTGfg6W7v16eDwUZtvWHNjWmphJB16Hg+vLuUb5V0D+S5nK3EDRo3aHMqNTc1pFBUSDNDk2pj9u9vnw8/7PcuFper1ko12WnYaVs2nPDVJbeUZXwIc5PDkVJOLWQUphFMEVWJfkb08p6dzvzRz8Ov799ttzIjdlgUUclm58tmwQmKGpY1SWCEu1BzKm6k2y4MZaJCEDINmybGor9dd4++f3zx8O7iuDaaUi0dp+MCEfjmWLw2GmPJkBgRuMVq6vmtTq9T2Vqrpiw5ZRInQVgYY4ya5WzGUDbr8Xqe5ZKkXKA3F0cfrc8+7k5m3GhYF7mio+ztXlqo2/cGKzvd1vREfNgRYho+PorAxyJSv+3MumxiWKqX9bfXWq9WK7fXKnYmbFniHwncnDLd13YgAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/1c8b4d82412ce0658d13380d547a00fd/a331c/2021-11-24-cover.png" srcset="/static/1c8b4d82412ce0658d13380d547a00fd/36ca5/2021-11-24-cover.png 200w, /static/1c8b4d82412ce0658d13380d547a00fd/a3397/2021-11-24-cover.png 400w, /static/1c8b4d82412ce0658d13380d547a00fd/a331c/2021-11-24-cover.png 800w, /static/1c8b4d82412ce0658d13380d547a00fd/d2429/2021-11-24-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In CQRS, it’s common to define interfaces for the handlers to enforce the unified code structure. Such an approach is used, e.g. by <a href="https://github.com/jbogard/MediatR">MediatR</a> library. It is a simple, in-memory implementation of the Mediator pattern. Even if you’re not using any libraries but handmade tools and you’re in the .NET or Java land, you may want to walk that way. It’s a longer discussion if DI is really needed. I may write a dedicated post around it, but today let’s assume that’s your desired approach.</p> <p>Registering handlers manually has benefits. You can use code as documentation of what you’re handling. Still, in some cases, it may become tedious, especially when working with a huge monolith. Also, sometimes you just want to have your life easier a bit. <a href="https://twitter.com/khellang">Kristian Hellang</a> created <a href="https://github.com/khellang/Scrutor">Scrutor</a> library that makes assembly scanning and DI registration extremely simple. Before we see how Scrutor can help us, have a look at the manual way.</p> <p>Let’s define a few interfaces for handling:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ICommandHandler<span class="token punctuation">&lt;</span><span class="token keyword">in</span> T<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token return-type class-name">ValueTask</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">T</span> command<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IQueryHandler<span class="token punctuation">&lt;</span><span class="token keyword">in</span> T<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>TResult<span class="token punctuation">></span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">T</span> query<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token return-type class-name">ValueTask</span> <span class="token generic-method"><span class="token function">Handle</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>T <span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>If we did manual registration, we could define the following methods:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Config</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token generic-method"><span class="token function">AddCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">,</span> TCommandHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TCommandHandler</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">ICommandHandler<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommandHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ICommandHandler<span class="token punctuation">&lt;</span>TCommand<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>sp <span class="token operator">=></span> sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TCommandHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token generic-method"><span class="token function">AddQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TQuery<span class="token punctuation">,</span> TQueryResult<span class="token punctuation">,</span> TQueryHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TQueryHandler</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">IQueryHandler<span class="token punctuation">&lt;</span>TQuery<span class="token punctuation">,</span> TQueryResult<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TQueryHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IQueryHandler<span class="token punctuation">&lt;</span>TQuery<span class="token punctuation">,</span> TQueryResult<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>sp <span class="token operator">=></span> sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TQueryHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token generic-method"><span class="token function">AddEventHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TEventHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TEventHandler</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span><span class="token punctuation">,</span> <span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEventHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEventHandler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span>sp <span class="token operator">=></span> sp<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEventHandler<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We could use them like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CartsConfig</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">AddHandlers</span><span class="token punctuation">(</span><span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token punctuation">{</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>InitializeCart<span class="token punctuation">,</span> HandleInitializeCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>AddProduct<span class="token punctuation">,</span> HandleAddProduct<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>GetCartById<span class="token punctuation">,</span> CartDetails<span class="token punctuation">?</span><span class="token punctuation">,</span> HandleGetCartById<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>GetCartAtVersion<span class="token punctuation">,</span> CartDetails<span class="token punctuation">,</span> HandleGetCartAtVersion<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddEventHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>CartConfirmed<span class="token punctuation">,</span> HandleCartFinalized<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>That doesn’t look bad, but such config, if not appropriately controlled, can grow exponentially (check <a href="/en/how_to_slice_the_codebase_effectively/">How to slice the codebase effectively?</a> to know how to tame it).</p> <p>Let’s see how we could use Scrutor to register all of our handlers automatically.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Config</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token function">AddAllCommandHandlers</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name">ServiceLifetime</span> withLifetime <span class="token operator">=</span> ServiceLifetime<span class="token punctuation">.</span>Transient<span class="token punctuation">,</span> <span class="token class-name">AssemblySelector</span> from <span class="token operator">=</span> AssemblySelector<span class="token punctuation">.</span>ApplicationDependencies<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> services<span class="token punctuation">.</span><span class="token function">Scan</span><span class="token punctuation">(</span>scan <span class="token operator">=></span> scan <span class="token punctuation">.</span><span class="token function">FromAssemblies</span><span class="token punctuation">(</span>from<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddClasses</span><span class="token punctuation">(</span>classes <span class="token operator">=></span> classes<span class="token punctuation">.</span><span class="token function">AssignableTo</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">ICommandHandler<span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>c <span class="token operator">=></span> <span class="token operator">!</span>c<span class="token punctuation">.</span>IsAbstract <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>c<span class="token punctuation">.</span>IsGenericTypeDefinition<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AsSelfWithInterfaces</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">WithLifetime</span><span class="token punctuation">(</span>withLifetime<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token function">AddAllQueryHandlers</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name">ServiceLifetime</span> withLifetime <span class="token operator">=</span> ServiceLifetime<span class="token punctuation">.</span>Transient<span class="token punctuation">,</span> <span class="token class-name">AssemblySelector</span> from <span class="token operator">=</span> AssemblySelector<span class="token punctuation">.</span>ApplicationDependencies<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> services<span class="token punctuation">.</span><span class="token function">Scan</span><span class="token punctuation">(</span>scan <span class="token operator">=></span> scan <span class="token punctuation">.</span><span class="token function">FromAssemblies</span><span class="token punctuation">(</span>from<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddClasses</span><span class="token punctuation">(</span>classes <span class="token operator">=></span> classes<span class="token punctuation">.</span><span class="token function">AssignableTo</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">IQueryHandler<span class="token punctuation">&lt;</span><span class="token punctuation">,</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>c <span class="token operator">=></span> <span class="token operator">!</span>c<span class="token punctuation">.</span>IsAbstract <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>c<span class="token punctuation">.</span>IsGenericTypeDefinition<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AsSelfWithInterfaces</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">WithLifetime</span><span class="token punctuation">(</span>withLifetime<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token function">AddAllEventHandlers</span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name">ServiceLifetime</span> withLifetime <span class="token operator">=</span> ServiceLifetime<span class="token punctuation">.</span>Transient<span class="token punctuation">,</span> <span class="token class-name">AssemblySelector</span> from <span class="token operator">=</span> AssemblySelector<span class="token punctuation">.</span>ApplicationDependencies<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> services<span class="token punctuation">.</span><span class="token function">Scan</span><span class="token punctuation">(</span>scan <span class="token operator">=></span> scan <span class="token punctuation">.</span><span class="token function">FromAssemblies</span><span class="token punctuation">(</span>from<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddClasses</span><span class="token punctuation">(</span>classes <span class="token operator">=></span> classes<span class="token punctuation">.</span><span class="token function">AssignableTo</span><span class="token punctuation">(</span><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">IEventHandler<span class="token punctuation">&lt;</span><span class="token punctuation">></span></span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>c <span class="token operator">=></span> <span class="token operator">!</span>c<span class="token punctuation">.</span>IsAbstract <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>c<span class="token punctuation">.</span>IsGenericTypeDefinition<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AsSelfWithInterfaces</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">WithLifetime</span><span class="token punctuation">(</span>withLifetime<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Scrutor allows us to define which assemblies we want to scan by calling the <em>FromAssemblies</em> method. We can select only our application dependencies with <em>AssemblySelector.ApplicationDependencies</em>.</p> <p>Then we can register implementations by calling <em>AddClasses</em> and selecting which we’d like to use. In our case, those will be not abstract, not generic classes implementing the particular interface (<em>ICommandHandler</em>, <em>IQueryHandler</em>, <em>IEventHandler</em>). We’re adding that condition not to register base classes or generic handlers. If we have such, then we should instead set them up manually.</p> <p>Then we can state that we want to register the handler as itself (so specific class) and all the interfaces it implements. Then we can call those methods in our application setup:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">services<span class="token punctuation">.</span><span class="token function">AddAllCommandHandlers</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddAllQueryHandlers</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">AddAllEventHandlers</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Such an approach will both work for custom handling like, e.g.:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CommandBus</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ICommandHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">GetCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">HttpContext</span> context<span class="token punctuation">)</span> <span class="token operator">=></span> context<span class="token punctuation">.</span>RequestServices<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ICommandHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ValueTask</span> <span class="token generic-method"><span class="token function">SendCommand</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">HttpContext</span> context<span class="token punctuation">,</span> <span class="token class-name">T</span> command<span class="token punctuation">)</span> <span class="token operator">=></span> context<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> context<span class="token punctuation">.</span>RequestAborted<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>You can expand it with decorators, etc., to build your pipelines and decorate handling with middlewares.</p> <p>Et voilà!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. if you liked this article, then check also similar:</p> <ul> <li><a href="/en/how_to_build_simple_event_pipeline">How to build a simple event pipeline</a></li> <li><a href="/en/cqrs_is_simpler_than_you_think_with_net6/">CQRS is simpler than you think with .NET 6 and C# 10</a></li> </ul><![CDATA[Long-polling, how to make our async API synchronous]]>https://event-driven.io/en/long_polling_and_eventual_consistency/https://event-driven.io/en/long_polling_and_eventual_consistency/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e0e58f97a3d42fd97504d5feec903331/d2429/2021-11-17-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA6/AAAOvwE4BVMkAAAB+klEQVQoz3VS32vTUBTuP+SDorC92IeBgih7kU3EVRgy3LDbBEUR5tqBZdaKP7Z2iEtXcVjXiQ5mWJPW4DZ/gQ/KmMMk7TWjFZN02uKPx8sn96bpkrE9fJwTzvm+fOecG7B1GQ4kHi1N4nDzWqmALVJErSzz3FTzvrrLcxGwdAkMtibz6G3+SYqorL/E+runUD8+Q+WLiN+V1zt+6vBagkzIFeN5U+wXeYWNDzm8mE1gTohhMXsH46ODmEuPY6tcdDiuQ83n0HW27ZCNZqwt4v7NK8ikxjCfiaO4kIIwMYJLF0IQc/dQ31T84zddBmw+MnPpRNZUNxS8L2TQ030CHYfbMNx/BuG+05i6fQ2xkTDSyQjsknePPkHnIJbmxB9f8/hbXcH8ozjO93bh1tgQ4tEhHD8axMED+9ARbMfsgxt8Jd5d2l6HOwX/fV9BVoih7dB+hE514tiRIC4O9OByOIT+3i5MT0TQMJhg3nNpae8rs6WXPy1g4Fw3+s6ehDB5HWLuLp4/TiAeHcabJQGN1g6bvJZDTaYMli7xaOsyNVWJ1g2Frr3N0mTiKp1JjtJMKkrTkxG6LD7kNUtz+7d5DHu+Q1N1nk79m4LPq0+wujSN6oaIhqH4xvQ6tPjImkR2g63LxFQlYqp50thUyJ/qMqmVCvyb1Rh24/0HFbfLQb3RL2EAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e0e58f97a3d42fd97504d5feec903331/a331c/2021-11-17-cover.png" srcset="/static/e0e58f97a3d42fd97504d5feec903331/36ca5/2021-11-17-cover.png 200w, /static/e0e58f97a3d42fd97504d5feec903331/a3397/2021-11-17-cover.png 400w, /static/e0e58f97a3d42fd97504d5feec903331/a331c/2021-11-17-cover.png 800w, /static/e0e58f97a3d42fd97504d5feec903331/d2429/2021-11-17-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I’ll continue today a topic of handling eventual consistency that I started in <a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">the previous article</a>. This time let’s learn the trick called “long-polling”. It helps in cheating on the API surface that our operations are synchronous.</p> <p>Let’s imagine that we’re either have an asynchronous process making changes, or just our database (e.g. MongoDB, ElasticSearch) has built-in eventual consistency. Having that, we cannot be sure if changes were already applied or not. The best was if we had a push notifications mechanism (e.g. WebSockets-based), informing the client application about the end of processing. Then the client could take the needed operation.</p> <p>However, for various reasons, this might not be a viable solution. Sometimes we don’t have enough experience with such solutions, and our coworkers get defensive. WebSockets are nice but tricky to configure, especially if we’re using load balancers. We may also be making an API-first approach where API is treated as our product. Some of our clients might not want to use push notifications. It’s also a mind shift experience, and UI/UX needs to reflect the asynchronous nature of backend logic.</p> <p>Too often, we decide to perform retries on our clients until we get satisfying data. I had the case once when client applications were doing DDoS attach on our backend, right in the middle of the important demo for the client. Blind client-side retries are no-go for the production systems.</p> <p>What to do then? We can consider using long-polling. We’re shifting retries from the clients to the backend side. Instead of being banged by the API retries, we’re waiting until the operation is finished or retrying the calls internally. Thanks to that, we’re getting more control of them. We can easier include logic like backpressure, circuit breakers etc.</p> <p>How to implement it? I’ll use NodeJS and TypeScript endpoint as an example. In other environments, it can be done accordingly. Let’s say that we’re opening the shopping cart. We’re processing the change and storing the data. We’re using MongoDB, which doesn’t guarantee reading-own-writes by default.</p> <p>Our handler looks as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">route</span> <span class="token operator">=</span> <span class="token punctuation">(</span>router<span class="token operator">:</span> Router<span class="token punctuation">)</span> <span class="token operator">=></span> router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/:shoppingCartId'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> Request<span class="token punctuation">,</span> response<span class="token operator">:</span> Response<span class="token punctuation">,</span> next<span class="token operator">:</span> NextFunction<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> query <span class="token operator">=</span> <span class="token function">mapRequestToQuery</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getShoppingCartDetails</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span>result <span class="token operator">===</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> response<span class="token punctuation">.</span><span class="token function">sendStatus</span><span class="token punctuation">(</span><span class="token number">404</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> response<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'ETag'</span><span class="token punctuation">,</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>revision<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">next</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">mapRequestToQuery</span><span class="token punctuation">(</span> request<span class="token operator">:</span> Request <span class="token punctuation">)</span><span class="token operator">:</span> GetShoppingCartDetails <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">'Invalid request'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re getting shopping cart id from the URL, getting the result together with ETag to enable optimistic concurrency handling (read more in <a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">How to use ETag header for optimistic concurrency</a>). The query handling is using MongoDB api:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">GetShoppingCartDetails</span> <span class="token operator">=</span> Query<span class="token operator">&lt;</span> <span class="token string">'get-shopping-cart-details'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">getShoppingCartDetails</span><span class="token punctuation">(</span> query<span class="token operator">:</span> GetShoppingCartDetails <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>ShoppingCartDetails <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> collection <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token generic-function"><span class="token function">getMongoCollection</span><span class="token generic class-name"><span class="token operator">&lt;</span>ShoppingCartDetails<span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token string">'shoppingCartDetails'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> collection<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> query<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>So far, so good. But what will happen if the newly opened shopping cart does not exist yet? We’ll get an unexpected <em>404</em> status. It’s getting more probable depending on how unlucky we are or how fast we’re making requests.</p> <p>To do <em>long-polling</em>, we need to retry our query until the result is available. We’ll use for that <em>retryPromise</em> and <em>retryIfNotFound</em> methods introduced in <a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">the previous article</a>.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">RetryOptions</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> maxRetries<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> delay<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> shouldRetry<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>error<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">DEFAULT_RETRY_OPTIONS</span><span class="token operator">:</span> Required<span class="token operator">&lt;</span>RetryOptions<span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> maxRetries<span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span> delay<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token function-variable function">shouldRetry</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">retryPromise</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">=</span> <span class="token builtin">never</span><span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token function-variable function">callback</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> options<span class="token operator">:</span> RetryOptions <span class="token operator">=</span> <span class="token constant">DEFAULT_RETRY_OPTIONS</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> retryCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> maxRetries<span class="token punctuation">,</span> delay<span class="token punctuation">,</span> shouldRetry<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span><span class="token constant">DEFAULT_RETRY_OPTIONS</span><span class="token punctuation">,</span> <span class="token operator">...</span>options<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">do</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">shouldRetry</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token operator">||</span> retryCount <span class="token operator">==</span> maxRetries<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[retry] Exceeded max retry count, throwing: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span> error<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> sleepTime <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> retryCount<span class="token punctuation">)</span> <span class="token operator">*</span> delay <span class="token operator">+</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> delay<span class="token punctuation">;</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[retry] Retrying (number: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span> retryCount <span class="token operator">+</span> <span class="token number">1</span> <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, delay: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>sleepTime<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">): </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span>sleepTime<span class="token punctuation">)</span><span class="token punctuation">;</span> retryCount<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">assertFound</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> find<span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token operator">===</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">'DOCUMENT_NOT_FOUND'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">retryIfNotFound</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token function-variable function">find</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span><span class="token punctuation">,</span> options<span class="token operator">:</span> RetryOptions <span class="token operator">=</span> <span class="token constant">DEFAULT_RETRY_OPTIONS</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">retryPromise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">assertFound</span><span class="token punctuation">(</span>find<span class="token punctuation">)</span><span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Method <em>assertFound</em> throws an exception, when the record was not found, to trigger promise rejection and retry made by <em>retryPromise</em>. It uses <a href="https://docs.aws.amazon.com/general/latest/gr/api-retries.html">the recommended by AWS retry policy</a>. It has configurable exponential backoff to increase the delay between retries, a random factor to not spam our database.</p> <p>We can wrap our query handler with them:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">route</span> <span class="token operator">=</span> <span class="token punctuation">(</span>router<span class="token operator">:</span> Router<span class="token punctuation">)</span> <span class="token operator">=></span> router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/:shoppingCartId'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> Request<span class="token punctuation">,</span> response<span class="token operator">:</span> Response<span class="token punctuation">,</span> next<span class="token operator">:</span> NextFunction<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> query <span class="token operator">=</span> <span class="token function">mapRequestToQuery</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">retryIfNotFound</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">await</span> <span class="token function">getShoppingCartDetails</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'ETag'</span><span class="token punctuation">,</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>revision<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>error <span class="token operator">===</span> <span class="token string">'DOCUMENT_NOT_FOUND'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> response<span class="token punctuation">.</span><span class="token function">sendStatus</span><span class="token punctuation">(</span><span class="token number">404</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">next</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Thanks to that, we can tune the retry options to get the expected result. However, as I explained in <a href="/en/tell_dont_ask_how_to_keep_an_eye_on_boiling_milk/">Tell, don’t ask! Or, how to keep an eye on boiling milk</a> article, relying on the timing is never okay. There may be the case when we don’t calculate timeout properly, and the document will still be unavailable. We need to be prepared for such a case. We cannot just increase the number of maximum retries. This will keep a hanging connection to our service and may cause connection pool exhaustion. The best is to fail fast and recover. It’s always good to provide a maximum deadline to cut it if it takes longer.</p> <p>We can do that by adding an additional method that will cut the async call. In JS/TS we can extend Promise as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">declare</span> global <span class="token punctuation">{</span> <span class="token keyword">interface</span> <span class="token class-name"><span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span> <span class="token punctuation">{</span> <span class="token function">withTimeout</span><span class="token punctuation">(</span>timeout<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name"><span class="token constant">TIMEOUT_ERROR</span></span> <span class="token operator">=</span> <span class="token string">'TIMEOUT_ERROR'</span><span class="token punctuation">;</span> <span class="token builtin">Promise</span><span class="token punctuation">.</span>prototype<span class="token punctuation">.</span><span class="token function-variable function">withTimeout</span> <span class="token operator">=</span> <span class="token keyword">function</span> <span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">(</span>timeout<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token builtin">Promise</span><span class="token punctuation">.</span><span class="token generic-function"><span class="token function">race</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">[</span> <span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token class-name"><span class="token builtin">Promise</span></span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span>_resolve<span class="token punctuation">,</span> reject<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">setTimeout</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">reject</span><span class="token punctuation">(</span><span class="token string">'TIMEOUT_ERROR'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> timeout<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>We’re using <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race">Promise.race</a> method, which takes an array of promises and finishes immediately when one of the promises succeeds or fail. We’re passing two promises:</p> <ul> <li>first one with our async call,</li> <li>second one is with a timer that will just reject promise after the defined timeout.</li> </ul> <p>If our call is fast enough, it’ll just return the result, and the timer promise will be finished. Otherwise, the timer will end our async call.</p> <p>We’re also extending the general <em>Promise</em> type by extending its prototype. Thanks to that, we can use it as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">retryIfNotFound</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">await</span> <span class="token function">getShoppingCartDetails</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">withTimeout</span><span class="token punctuation">(</span><span class="token number">1000</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Which will kill our retries if they take longer than expected.</p> <p>Long-polling is a simple technique that shouldn’t be used by default. We should at first consider changing our UI/UX strategy, consider using push notifications. However, sometimes we just have to pragmatically get the job done, and that’s one of the tools that used wisely can help us on that.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Dealing with Eventual Consistency and Idempotency in MongoDB projections]]>https://event-driven.io/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/https://event-driven.io/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/<p>Auditability, diagnostics, time travelling are usually the first mentioned features when speaking about Event Sourcing. All of them are great, but to me, projections are the real killer feature of an event-driven approach. Why?</p> <p>Projections are different interpretations of the same fact. Look at the photo below. Muhammad Ali is standing in triumph over Sonny Liston. Each of them interprets the same fact from an entirely different perspective.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/fa859cd5928f912b1cbe4691bb92d7b6/d2429/2021-11-10-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACgElEQVQozzXOS08TQQDA8enO7M7s7uyju9vXltJSLVCwRXlKTQDbAprQQEFAaZUoDwOkvMQAMT4CGrRKgCCCEgIeSMCLCUfi1cQYDyQK0TPxe5ga/QC//P+AYRCEGCHCEZlQm2R4iMN/KVQWr6p2BssVb4nuO6fmB2UzIOgeTG0M5CVFl602BmLwD0OCscSLGkdtnryC2ZboyOXIw6ZGvyfA232K009t+YQ6iKBBhKmsS6odAAAsFgZCFkHMsjzmKUtUQzXuN1T2VYVGI7VnnV5sNSU9T1BcWDByNcBohhsTIYf/ljnE8hhTwsuCqCIit0Qik53J6vMXvKZPVE3N4dVcfk40SsOVNZE6TKhIFQai/xgRjqOEyBLVEOI3t96fnv7+fPhpoD5KqK7aParhJqLm8Qby8/0QEiobEGEAIcptI5LbxiIVVQTx6tyTlYXnHzbWWyoqeVmXrQ5B0lhWcLkLGqLNEBEqygBYAIQQIZbjCOYEggUqykRQWkNF1X5frMhf7i8gVBOp1W44OVZwaUYwUGhaNcRykIFAkSSe8AJPBV5UJVWmaqHLnIxeHElcTSdab9RUVJWGA8Vl6Xi8Ixp70D94J92rK7pArbJqA7FQccjrOeN215cW1YWC8UjtdE/nfGusLhzuakuuZQay98Zmhu5mJ8ZvNTdmUjcT0RgAFkVS7XYT7G6u7W5vLb3M7my82VldznS2L/ane2L1iuGYSyVPNhYOpya6KsrmZ6YXHz1eWX499yw7OjwyNDjYnuwAx9+Pfh7/2H73dulVdmf5Raa7bfZ2ajidGrt+bSoRX7/S9DRQYmpGb3f3fl//x8z40cmvL1+/HRwc7O/t/QEUu4C7EcNqTwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/fa859cd5928f912b1cbe4691bb92d7b6/a331c/2021-11-10-cover.png" srcset="/static/fa859cd5928f912b1cbe4691bb92d7b6/36ca5/2021-11-10-cover.png 200w, /static/fa859cd5928f912b1cbe4691bb92d7b6/a3397/2021-11-10-cover.png 400w, /static/fa859cd5928f912b1cbe4691bb92d7b6/a331c/2021-11-10-cover.png 800w, /static/fa859cd5928f912b1cbe4691bb92d7b6/d2429/2021-11-10-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>The same happens in our systems. Let’s take a shopping cart as an example. After we added a product item to the shopping cart, we have to:</p> <ul> <li>update details of the shopping cart,</li> <li>increase the total amount in the summary view,</li> <li>adjust the number of products in the inventory,</li> <li>send a notification in the user menu.</li> </ul> <p>All of that is triggered by a single <em>ProductItemAddedToShoppingCart</em> event.</p> <p>Projections have multiple advantages for the development process. The one that I’d like to highlight especially is reducing the cognitive load. You can break down a process into two parts. At first, on modelling the business logic and capturing its result. Then thinking about how to interpret it. This is a liberating experience from thinking all at once in the relational approach. “Gosh, when I add that field, then this will break foreign key. How do I do this migration?“. Of course, we’ll eventually need to think about connecting our event with the read model, but we can split that thought process.</p> <p>Projections are powerful but also non-trivial. In theory, it’s just a left-fold approach: you take the current state and apply event data, getting a new one. Yet, things can get more complex if we want a robust, fault-tolerant solution. Especially, the distributed environment may be challenging. As I described in the <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">Outbox, Inbox patterns and delivery guarantees explained</a>, to be sure that our events are processed, we need to include durable storage, retry policies etc. That’s how <a href="https://developers.eventstore.com/clients/grpc/subscriptions.html">EventStoreDB subscriptions</a>, <a href="https://martendb.io/events/projections/async-daemon.html">Marten’s Async Daemon</a>, <a href="https://www.oreilly.com/library/view/kafka-the-definitive/9781491936153/ch04.html">Kafka Consumers</a> work. We get more onto our plate by making solution resilient and fault-tolerant, e.g. eventual consistency and idempotency handling. We don’t know when something will be processed. We also don’t know how many times we’ll get the same message. We need to take that into account while designing our solution.</p> <p>Some databases add more to that, e.g. MongoDB and ElasticSearch have latency between being able to read our writes. Recently I was hit by that.</p> <p>Let’s start with the business requirements. We’ll be modelling the shopping cart process. We can add or remove items to open shopping carts (which have not yet been confirmed). When adding a new product item, the new element is added to the list. If we’re adding an already existing product item, we should increase the quantity instead of adding a new element. Accordingly, if we’re removing a product, we should decrease the quantity unless we zeroed it. Then we have to remove the whole item from the list.</p> <p>This could be coded in TypeScript as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItem</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> productId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> quantity<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">findProductItem</span><span class="token punctuation">(</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> productId<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">)</span><span class="token operator">:</span> ProductItem <span class="token operator">|</span> <span class="token keyword">undefined</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> productItems<span class="token punctuation">.</span><span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">===</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">addProductItem</span><span class="token punctuation">(</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> newProductItem<span class="token operator">:</span> ProductItem <span class="token punctuation">)</span><span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity <span class="token punctuation">}</span> <span class="token operator">=</span> newProductItem<span class="token punctuation">;</span> <span class="token keyword">const</span> currentProductItem <span class="token operator">=</span> <span class="token function">findProductItem</span><span class="token punctuation">(</span>productItems<span class="token punctuation">,</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>currentProductItem<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token operator">...</span>productItems<span class="token punctuation">,</span> newProductItem<span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newQuantity <span class="token operator">=</span> currentProductItem<span class="token punctuation">.</span>quantity <span class="token operator">+</span> quantity<span class="token punctuation">;</span> <span class="token keyword">const</span> mergedProductItem <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> newQuantity <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> productItems<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">===</span> productId <span class="token operator">?</span> mergedProductItem <span class="token operator">:</span> pi <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">removeProductItem</span><span class="token punctuation">(</span> productItems<span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> newProductItem<span class="token operator">:</span> ProductItem <span class="token punctuation">)</span><span class="token operator">:</span> ProductItem<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity <span class="token punctuation">}</span> <span class="token operator">=</span> newProductItem<span class="token punctuation">;</span> <span class="token keyword">const</span> currentProductItem <span class="token operator">=</span> <span class="token function">findProductItem</span><span class="token punctuation">(</span>productItems<span class="token punctuation">,</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newQuantity <span class="token operator">=</span> <span class="token punctuation">(</span>currentProductItem<span class="token operator">?.</span>quantity <span class="token operator">??</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token operator">-</span> quantity<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>newQuantity <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token string">'PRODUCT_ITEM_NOT_FOUND'</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>newQuantity <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">return</span> productItems<span class="token punctuation">.</span><span class="token function">filter</span><span class="token punctuation">(</span><span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">!==</span> productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> mergedProductItem <span class="token operator">=</span> <span class="token punctuation">{</span> productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> newQuantity <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">return</span> productItems<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>pi<span class="token punctuation">)</span> <span class="token operator">=></span> pi<span class="token punctuation">.</span>productId <span class="token operator">===</span> productId <span class="token operator">?</span> mergedProductItem <span class="token operator">:</span> pi <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see, we need to have both the current state and new data to calculate the result correctly. We’re performing a merge, not just simple addition.</p> <p>Events in our process could look like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartOpened</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'shopping-cart-opened'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> clientId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> openedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemAddedToShoppingCart</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'product-item-added-to-shopping-cart'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'product-item-removed-from-shopping-cart'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> productItem<span class="token operator">:</span> ProductItem<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ShoppingCartConfirmed</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'shopping-cart-confirmed'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> confirmedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>The need to have the current state to process events is harder to fulfil on databases that are eventually consistent by default. MongoDB is one of them. The merging logic presented above may result in erroneous results if the decision is made on the stale state. When you open a shopping cart and immediately add and remove some products, such a case may happen. Accordingly, we’ll have the wrong result if we don’t support idempotency well and apply <em>ProductItemAddedToShoppingCart</em> twice or thrice.</p> <p>As I noted in the <a href="/en/state-obsession/">“State Obsession” article</a>, the alternative is modelling events differently. We could ask our business if the requirement for merging is a must. Maybe we could just add separate product items. This will work, assuming that write operations are linearizable and are applied in the right order. MongoDB allows granular update to the inner document collection (read more in <a href="https://docs.mongodb.com/manual/reference/operator/update/push/">docs</a>). If we could just add or remove an item, we wouldn’t need to get the current state to do that. Of course, we assume that the correctness of the operation was validated as the event was added to the write model, so we accept the event data as is.</p> <p>Nevertheless, the requirement can be set to stone. Then we can try to juggle tradeoffs in the event design. We could send the whole merged list in the event data. But we’ll lose the specific business information or duplicate the event data. It’s not the best approach, but pragmatically it can help us to resolve issues.</p> <p>Some events, by their nature, are idempotent. As confirmation, in our case, just update the status, then we could apply it multiple times. As long as we can have an ordering guarantee (like EventStoreDB or Marten), applying the same event multiple times won’t harm us.</p> <p>Still, sometimes making decisions on the current state is unavoidable. What do to then?</p> <p>Adding a new item is not affected by eventual consistency, as we’re not reading anything, e.g.:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">projectShoppingCartOpened</span><span class="token punctuation">(</span> event<span class="token operator">:</span> ShoppingCartOpened<span class="token punctuation">,</span> streamRevision<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>Result<span class="token operator">&lt;</span><span class="token builtin">boolean</span><span class="token operator">>></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">shoppingCartsCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> shoppingCarts<span class="token punctuation">.</span><span class="token function">insertOne</span><span class="token punctuation">(</span><span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>clientId<span class="token punctuation">,</span> status<span class="token operator">:</span> ShoppingCartStatus<span class="token punctuation">.</span>Opened<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> productItems<span class="token operator">:</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> openedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>openedAt<span class="token punctuation">,</span> revision<span class="token operator">:</span> streamRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">success</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Problems arise on the first update. If we rapidly add a new product to the shopping cart, then such code may not be enough:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">projectProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> event<span class="token operator">:</span> ProductItemAddedToShoppingCart<span class="token punctuation">,</span> streamRevision<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>Result<span class="token operator">&lt;</span><span class="token builtin">boolean</span><span class="token operator">>></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">shoppingCartsCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productItems <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> shoppingCarts<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> revision<span class="token operator">:</span> streamRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> projection<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> shoppingCarts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token function">addProductItem</span><span class="token punctuation">(</span>productItems<span class="token punctuation">,</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">success</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Why this may fail? For various reasons. The first one is eventual consistency. The shopping cart opening may not be fully processed, and we don’t see the new read model. Then <em>findOne</em> will return not expected data.</p> <p>What’s more, we may be lucky, and the first product item addition succeed. But if we have immediately added another product, then the first update may not be yet processed. We may read the shopping cart state, getting a stale result with an empty cart. Then we may accidentally overwrite the state with the wrong value. To ensure we have the right state, we need to include the aggregate version/stream revision (read more in <a href="/en/lets_talk_about_positions_in_event_stores/">Let’s talk about positions in event stores</a>). EventStoreDB subscriptions, by default, provide you with this information. We can use it in the <em>findOne</em> condition to check if the document is in the expected state. Then use it for the <a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">optimistic concurrency check</a> while updating the read model state.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">projectProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> event<span class="token operator">:</span> ProductItemAddedToShoppingCart<span class="token punctuation">,</span> streamRevision<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>Result<span class="token operator">&lt;</span><span class="token builtin">boolean</span><span class="token operator">>></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">shoppingCartsCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> lastRevision <span class="token operator">=</span> streamRevision <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productItems<span class="token punctuation">,</span> revision <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> shoppingCarts<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> revision<span class="token operator">:</span> streamRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> projection<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> shoppingCarts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> revision<span class="token operator">:</span> lastRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token function">addProductItem</span><span class="token punctuation">(</span>productItems<span class="token punctuation">,</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> streamRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">success</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Thanks to that, we ensure that we’ll get a read model that matches the expected state: the one after processing the last event.</p> <p>Still, that’s just a guard. It’s not yet a guarantee of always getting the current state. By adding this condition, we’ll get <em>null</em> when the state is not at a particular version/revision. As the changes may still propagate, we have to retry reads until we get the expected state.</p> <p>AWS <a href="https://docs.aws.amazon.com/general/latest/gr/api-retries.html">suggests following retry policy</a>:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">Do some asynchronous operation. retries = 0 DO wait for (2^retries * 100) milliseconds status = Get the result of the asynchronous operation. IF status = SUCCESS retry = false ELSE IF status = NOT_READY retry = true ELSE IF status = THROTTLED retry = true ELSE Some other error occurred, so stop calling the API. retry = false END IF retries = retries + 1 WHILE (retry AND (retries &lt; MAX_RETRIES))</code></pre></div> <p>It’s reasonable. Thanks to exponential backoff, we’ll start retrying quicker but slow with each retry. We also have max retries to ensure that we won’t fall into an infinite loop. We could implement retry code based on that as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">RetryOptions</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> maxRetries<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> delay<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> shouldRetry<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>error<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token constant">DEFAULT_RETRY_OPTIONS</span><span class="token operator">:</span> Required<span class="token operator">&lt;</span>RetryOptions<span class="token operator">></span> <span class="token operator">=</span> <span class="token punctuation">{</span> maxRetries<span class="token operator">:</span> <span class="token number">5</span><span class="token punctuation">,</span> delay<span class="token operator">:</span> <span class="token number">100</span><span class="token punctuation">,</span> <span class="token function-variable function">shouldRetry</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">retryPromise</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">=</span> <span class="token builtin">never</span><span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token function-variable function">callback</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span><span class="token punctuation">,</span> options<span class="token operator">:</span> RetryOptions <span class="token operator">=</span> <span class="token constant">DEFAULT_RETRY_OPTIONS</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">let</span> retryCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> maxRetries<span class="token punctuation">,</span> delay<span class="token punctuation">,</span> shouldRetry<span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token operator">...</span><span class="token constant">DEFAULT_RETRY_OPTIONS</span><span class="token punctuation">,</span> <span class="token operator">...</span>options<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">do</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">await</span> <span class="token function">callback</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">shouldRetry</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token operator">||</span> retryCount <span class="token operator">==</span> maxRetries<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[retry] Exceeded max retry count, throwing: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">throw</span> error<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> sleepTime <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">pow</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">,</span> retryCount<span class="token punctuation">)</span> <span class="token operator">*</span> delay <span class="token operator">+</span> Math<span class="token punctuation">.</span><span class="token function">random</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">*</span> delay<span class="token punctuation">;</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">[retry] Retrying (number: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span> retryCount <span class="token operator">+</span> <span class="token number">1</span> <span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">, delay: </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>sleepTime<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">): </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>error<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">sleep</span><span class="token punctuation">(</span>sleepTime<span class="token punctuation">)</span><span class="token punctuation">;</span> retryCount<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">while</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>I also added a random factor. This is critical, especially if we have <em>cron</em> based logic. If we have multiple instances banging some service with the same cadence, we may overload it. Adding random will distribute the load. Of course, you should consider using tools, like <a href="https://github.com/App-vNext/Polly">Polly</a>, before writing your own code.</p> <p>As the final touch, let’s add a helper for retrying when the document was not found:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">assertFound</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> find<span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span><span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">find</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token operator">===</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">'DOCUMENT_NOT_FOUND'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">retryIfNotFound</span><span class="token generic class-name"><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token function-variable function">find</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span> <span class="token operator">|</span> <span class="token keyword">null</span><span class="token operator">></span><span class="token punctuation">,</span> options<span class="token operator">:</span> RetryOptions <span class="token operator">=</span> <span class="token constant">DEFAULT_RETRY_OPTIONS</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span><span class="token constant">T</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">retryPromise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">assertFound</span><span class="token punctuation">(</span>find<span class="token punctuation">)</span><span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We can also add a similar wrapper for updates:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">assertUpdated</span><span class="token punctuation">(</span> <span class="token function-variable function">update</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>UpdateResult<span class="token operator">></span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>UpdateResult<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">update</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result<span class="token punctuation">.</span>modifiedCount <span class="token operator">===</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">'FAILED_TO_UPDATE_DOCUMENT'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">retryIfNotUpdated</span><span class="token punctuation">(</span> <span class="token function-variable function">update</span><span class="token operator">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>UpdateResult<span class="token operator">></span><span class="token punctuation">,</span> options<span class="token operator">:</span> RetryOptions <span class="token operator">=</span> <span class="token constant">DEFAULT_RETRY_OPTIONS</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>UpdateResult<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">retryPromise</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">assertUpdated</span><span class="token punctuation">(</span>update<span class="token punctuation">)</span><span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Thanks to that, we can compose our logic using the wrappers:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">projectProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> event<span class="token operator">:</span> ProductItemAddedToShoppingCart<span class="token punctuation">,</span> streamRevision<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>Result<span class="token operator">&lt;</span><span class="token builtin">boolean</span><span class="token operator">>></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">shoppingCartsCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> lastRevision <span class="token operator">=</span> streamRevision <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productItems<span class="token punctuation">,</span> revision <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">retryIfNotFound</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> shoppingCarts<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> revision<span class="token operator">:</span> streamRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> projection<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">assertUpdated</span><span class="token punctuation">(</span> shoppingCarts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> revision<span class="token operator">:</span> lastRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token function">removeProductItem</span><span class="token punctuation">(</span>productItems<span class="token punctuation">,</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> streamRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">success</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Now we’re sure that we’ll get the expected state eventually. Adding the <em>revision: lastRevision</em> to the update filter and setting <em>revision: streamRevision</em> will ensure that we do not update the state more than once.</p> <p>Though, it’s not yet fully idempotent. We wouldn’t like to fail when we successfully added a product item and the event is processed again (because of some retry). We’d want to just skip processing. For that case, stream revision will be equal to the event revision, so we need to change the check to greater or equal (<em>$gte</em>) and <em>if</em> to skip processing. The final code will look like this:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token function">projectProductItemRemovedFromShoppingCart</span><span class="token punctuation">(</span> event<span class="token operator">:</span> ProductItemRemovedFromShoppingCart<span class="token punctuation">,</span> streamRevision<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>Result<span class="token operator">&lt;</span><span class="token builtin">boolean</span><span class="token operator">>></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCarts <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">shoppingCartsCollection</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> lastRevision <span class="token operator">=</span> streamRevision <span class="token operator">-</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">const</span> <span class="token punctuation">{</span> productItems<span class="token punctuation">,</span> revision <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">retryIfNotFound</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=></span> shoppingCarts<span class="token punctuation">.</span><span class="token function">findOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token punctuation">{</span> $gte<span class="token operator">:</span> lastRevision <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> projection<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> <span class="token number">1</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>revision <span class="token operator">></span> lastRevision<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">success</span><span class="token punctuation">(</span><span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">await</span> <span class="token function">assertUpdated</span><span class="token punctuation">(</span> shoppingCarts<span class="token punctuation">.</span><span class="token function">updateOne</span><span class="token punctuation">(</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> revision<span class="token operator">:</span> lastRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> $set<span class="token operator">:</span> <span class="token punctuation">{</span> productItems<span class="token operator">:</span> <span class="token function">removeProductItem</span><span class="token punctuation">(</span>productItems<span class="token punctuation">,</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> revision<span class="token operator">:</span> streamRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> upsert<span class="token operator">:</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">success</span><span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>You can check the whole processing code in my <a href="https://github.com/oskardudycz/EventSourcing.NodeJS/blob/optimistic_concurrency/samples/optimisticConcurrency/src/shoppingCarts/gettingById/projection.ts">NodeJS samples</a>.</p> <p>Of course, what I showed here is the handling of the single stream. For multiple, things get more complex. We either have to maintain various revisions for each stream or store the event’s global position in a separate collection. By that, we can enforce the uniqueness constraint, storing them together in the same transaction. Still, the general logic will remain the same.</p> <p>The same pattern can be applied to other databases with eventual consistency, e.g. ElasticSearch, CosmosDB, DynamoDB. In other languages, implementation will also look similar.</p> <p>The above explanation assumes that we have ordering guarantees on the event handler processing. For instance, if you add item A to Cart X, then remove it, you will always see the <em>ProductItemAddedToShoppingCart</em> event before the <em>ProductItemRemovedToShoppingCart</em>. EventStoreDB guarantees global ordering even between the streams (e.g. Kafka doesn’t have full ordering guarantees). Dealing with out of order events is a topic for a dedicated blog article.</p> <p>What’s your experience with eventual taming consistency and idempotency issues?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. if you liked this article check also:</p> <ul> <li><a href="/en/how_to_do_events_projections_with_entity_framework/">How to build event-driven projections with Entity Framework</a></li> <li><a href="/en/how_to_scale_projections_in_the_event_driven_systems/">How to scale projections in the event-driven systems?</a></li> <li><a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">How to create projections of events for nested object structures?</a></li> </ul><![CDATA[How to use ETag header for optimistic concurrency]]>https://event-driven.io/en/how_to_use_etag_header_for_optimistic_concurrency/https://event-driven.io/en/how_to_use_etag_header_for_optimistic_concurrency/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/98c41822715f54b5f0c5e04a41bcd708/d2429/2021-11-03-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AE1FNnZ2aIuPgWFxaENTZIaMinB+eVVURmJhUUlXV0lPTktVODk9Hy8vEFNePWJkS3uEgcuruKmmq4OVpAA+PC5ZUz1tZ01zclNpdG6HlphqeHtBSEAkHg4jKh8vPichJQ0eIw0gJA4wMRE6QiB4cFPPwMShpKaNlpkASkc7QjsrY1pEXVFBcGREkodsdnt1REc/JyclHykpHiUUEhYLGh0MFxoMGh8PChAAcHBcz8zBlZybj5KRAFVYSzYtIEVFMzUzKzMiFHhuY2lzfGJESC80MT0/OywnGSwqFSArGBQSCw0PCgMDAGljWJSanX2EgpSLiwCBfV9VXCFLVUAoMioaHxwzNDJYXFdnUlBDQDcbFg0iHBUNDQhAIBkZDQkMFQxINBaOdVSIkYxnXk+DeGgAhnpnaFI5SVFBSlY4JS4eLCwrVVBDPT4hRTIgIyEXWVNGRDQwThMXIhIOHCoZQDsiqquawcCxbVxAZldEAHF2d495am5UTZZPWTw0Kzk1LFBUTlVMMlBAMkYqHmxaUH9yV11WNXpaXXpDP19IMouEfJmLfGtbTYRiUgCjdlGZYjGNUx+eUyaCTyduWUNvZVh4Sj1jU0VvSjqGWEaGXkuDYEaYZVuXbWWgc1atgVqxjXC7j3nDk3YAajslbEgheU4iaU0nXj0ki4dnp45kxHhZt4ZvtZNpxpZuypd6t6uOwqOFtaSIu6KFuqWXrZWDn5ODoI6BAF9FKGJNNnRTSYONcZ+FZbGpi8WwmcqrjMqxmMi7ocarlLGnk4uSf46JeYiHeIB9b3p2anl3ZXh1ZHp4ZwCxr4XFrIbBq6HMvZbPnInhcGbhdFunhHWGg3B8dmpuaVlmYlRZV0hjXk9pYlFwbl6KdVd+ZlRqaVxdWEv58PZMc6b/owAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/98c41822715f54b5f0c5e04a41bcd708/a331c/2021-11-03-cover.png" srcset="/static/98c41822715f54b5f0c5e04a41bcd708/36ca5/2021-11-03-cover.png 200w, /static/98c41822715f54b5f0c5e04a41bcd708/a3397/2021-11-03-cover.png 400w, /static/98c41822715f54b5f0c5e04a41bcd708/a331c/2021-11-03-cover.png 800w, /static/98c41822715f54b5f0c5e04a41bcd708/d2429/2021-11-03-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In my article <a href="/en/optimistic_concurrency_for_pessimistic_times/">“Optimistic concurrency for the pesimistic times”</a>, I described the premises for optimistic concurrency handling. As a reminder, we assume that conflict situations will be rare. A conflict arises when two people try to change the same record at the same time. When this happens, we will only allow the first person to update the state. All other updates will be rejected. For verification, we use a record version that changes with each save.</p> <p>What does an optimistic concurrency implementation look like?</p> <ol> <li>Return the entity’s current version while reading the data.</li> <li>Modify the state and send it with the (unchanged) version.</li> <li>Check if the version from the database equals the expected version sent in the request.</li> <li>If they match, allow saving, make the change and set a new entity version in the database (e.g. increment it)</li> <li>If not, throw or return an error.</li> </ol> <p>So much for the theory. However, how, in practice, can we handle transferring the version between the web/mobile application and the application server?</p> <p>The <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag">ETag</a> header can help with that. Originally it was invented to aid cache handling.</p> <p>When the server returns a result, it computes a value representing the currently returned data. This value is passed as the response <em>ETag</em> header. It can be a hash or an obligatory value, e.g. a version number.</p> <p>When the client gets data from the server, it can cache the <em>ETag</em> header value and the data itself. Then, when it wants to get the latest state, it can pass the downloaded <em>ETag</em> value as the <em>If-None-Match</em> header. The server should only return new data if something has changed. Otherwise, it should return the status <a href="https://http.cat/304">304</a>. Based on that client either replaces the cached data or assume that nothing has changed (in the case of <em>304</em> status).</p> <p>This is precisely how browsers work. They have built-in support for <em>ETag</em> and <em>If-None-Match</em> headers and use it for caching the results. If we make a mistake in the algorithm calculating the <em>ETag</em> value, we can cause that client applications will not be able to refresh their cache. Of course, this may be dangerous, especially in the context of web applications.</p> <p><em>ETag</em> is have two formats:</p> <ul> <li><em>Strong</em>, a globally unique value,</li> <li><em>Weak</em> (with the prefix <em>W/</em>) is unique only in a particular context.</li> </ul> <p>The difference is similar to the <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier"><em>Uuid</em></a> and numeric identifiers in relational databases. <em>Uuid</em> is unique globally for the whole database; numeric, only in the context of a given table.</p> <p>We could model ETag in TypeScript as follows:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">WeakETag</span> <span class="token operator">=</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">W/</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span><span class="token builtin">string</span><span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">ETag</span> <span class="token operator">=</span> WeakETag <span class="token operator">|</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">isWeakETag</span><span class="token punctuation">(</span>etag<span class="token operator">:</span> ETag<span class="token punctuation">)</span><span class="token operator">:</span> etag <span class="token keyword">is</span> WeakETag <span class="token punctuation">{</span> <span class="token keyword">return</span> etag<span class="token punctuation">.</span><span class="token function">startsWith</span><span class="token punctuation">(</span><span class="token string">'W/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">getWeakETagValue</span><span class="token punctuation">(</span>etag<span class="token operator">:</span> WeakETag<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> etag<span class="token punctuation">.</span><span class="token function">substr</span><span class="token punctuation">(</span><span class="token string">'W/'</span><span class="token punctuation">.</span>length<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>value<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span><span class="token operator">:</span> WeakETag <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">W/"</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>value<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">"</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>For <em>ETag</em>, an example of the <em>strong</em> format would be concatenating the <em>Uuid</em> record’s identifier and its version. The <em>weak</em> format can be, e.g. numeric id joined with version (in the context of the whole collection) or just version (in the context of specific record).</p> <p>To use <em>ETag</em> for optimistic concurrency, we need to use the <em>If-Match</em> header. While sending a request to change the state (e.g. <em>PUT</em>), we should send the expected state version as the value of the <em>If-Match</em> header. The server should check if the <em>ETag</em> value is equal to the current one. If it equals, save succeed. Otherwise, it should send the <a href="https://http.cat/412">412</a> response status.</p> <p>Example code for parsing the <em>ETag</em> value from <em>If-Match</em> header in TypeScript:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">getETagFromIfMatch</span><span class="token punctuation">(</span> request<span class="token operator">:</span> Request <span class="token punctuation">)</span><span class="token operator">:</span> ETag <span class="token punctuation">{</span> <span class="token keyword">const</span> etag <span class="token operator">=</span> request<span class="token punctuation">.</span>headers<span class="token punctuation">[</span><span class="token string">'if-match'</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>etag <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">'MISSING_IF_MATCH_HEADER'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token operator">&lt;</span>ETag<span class="token operator">></span>etag<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">getWeakETagValueFromIfMatch</span><span class="token punctuation">(</span> request<span class="token operator">:</span> Request <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> etag <span class="token operator">=</span> <span class="token function">getETagFromIfMatch</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isWeakETag</span><span class="token punctuation">(</span>etag<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token string">'WRONG_WEAK_ETAG_FORMAT'</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">getWeakETagValue</span><span class="token punctuation">(</span>etag<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Let’s see how we could handle ETags and optimistic concurrency using the shopping cart flow example. We’re running the Black Friday frenzy together with our partner. Still, we’d like to be sure that we know what we’re doing and, e.g. do not order the same stuff twice.</p> <ol> <li>Get the current shopping cart together with it’s version send as a <em>weak ETag</em>:</li> </ol> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">curl</span> -i we-buy-everything.com/api/clients/595d54aa/shopping-carts/ce601dc7 HTTP/1.1 <span class="token number">200</span> OK ETag: W/<span class="token string">"1"</span> <span class="token punctuation">{</span> id: 595d54aa, productItems: <span class="token punctuation">[</span><span class="token punctuation">]</span>, revision: <span class="token number">1</span> <span class="token punctuation">}</span></code></pre></div> <p>Implementation in Express NodeJS framework could look like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">route</span> <span class="token operator">=</span> <span class="token punctuation">(</span>router<span class="token operator">:</span> Router<span class="token punctuation">)</span> <span class="token operator">=></span> router<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/:shoppingCartId'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> Request<span class="token punctuation">,</span> response<span class="token operator">:</span> Response<span class="token punctuation">,</span> next<span class="token operator">:</span> NextFunction<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> query <span class="token operator">=</span> <span class="token function">mapRequestToQuery</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getShoppingCartDetails</span><span class="token punctuation">(</span>query<span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'ETag'</span><span class="token punctuation">,</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>revision<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">send</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">next</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">mapRequestToQuery</span><span class="token punctuation">(</span> request<span class="token operator">:</span> Request <span class="token punctuation">)</span><span class="token operator">:</span> GetShoppingCartDetails <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">isNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">'Invalid request'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre></div> <p><em><strong>Note:</strong> If you want to use custom ETag handling in Express, you have to disable the default behaviour:</em></p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> app<span class="token operator">:</span> Application <span class="token operator">=</span> <span class="token function">express</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> app<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'etag'</span><span class="token punctuation">,</span> <span class="token boolean">false</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <ol start="2"> <li>Modify the state (e.g. add a new product item to the shopping cart) and send it together with the (unchanged) version.</li> </ol> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">curl</span> -i -X POST <span class="token punctuation">\</span> -H <span class="token string">'Content-Type: application/json'</span> <span class="token punctuation">\</span> -d <span class="token string">'{ "productId":"4f3321fc", "quantity": 1 }'</span> <span class="token punctuation">\</span> -H <span class="token string">'If-Match: W/"1"'</span> <span class="token punctuation">\</span> we-buy-everything.com/api/clients/595d54aa/shopping-carts/ce601dc7/product-items HTTP/1.1 <span class="token number">200</span> OK ETag: W/<span class="token string">"2"</span></code></pre></div> <ol start="3"> <li>When someone else tries to update the state with the obsolete value (e.g. your partner trying to add the same product), return an <em>412</em> error code:</li> </ol> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">curl</span> -i -X POST <span class="token punctuation">\</span> -H <span class="token string">'Content-Type: application/json'</span> <span class="token punctuation">\</span> -d <span class="token string">'{ "productId":"4f3321fc", "quantity": 3 }'</span> <span class="token punctuation">\</span> -H <span class="token string">'If-Match: W/"1"'</span> <span class="token punctuation">\</span> we-buy-everything.com/api/clients/595d54aa/shopping-carts/ce601dc7/product-items HTTP/1.1 <span class="token number">412</span> Precondition Failed</code></pre></div> <ol start="4"> <li>The client must then get the latest state together with new version.</li> </ol> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">curl</span> -i we-buy-everything.com/api/clients/595d54aa/shopping-carts/ce601dc7 HTTP/1.1 <span class="token number">200</span> OK ETag: W/<span class="token string">"2"</span> <span class="token punctuation">{</span> id: ce601dc7-ea93-4b7c-879a-bdb4c187adfa, productItems: <span class="token punctuation">[</span><span class="token punctuation">{</span> productId:4f3321fc, quantity: <span class="token number">1</span> <span class="token punctuation">}</span><span class="token punctuation">]</span> revision: <span class="token number">2</span> <span class="token punctuation">}</span></code></pre></div> <ol start="5"> <li>And then make the change again, if it makes sense, using the new value from the <em>ETag</em> header:</li> </ol> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash">$ <span class="token function">curl</span> -i -X PUT <span class="token punctuation">\</span> -H <span class="token string">'Content-Type: application/json'</span> <span class="token punctuation">\</span> -d <span class="token string">'{ "productId":"4f3321fc", "quantity": 3 }'</span> <span class="token punctuation">\</span> -H <span class="token string">'If-Match: W/"2"'</span> <span class="token punctuation">\</span> we-buy-everything.com/api/clients/595d54aa/shopping-carts/ce601dc7 HTTP/1.1 <span class="token number">200</span> OK ETag: W/<span class="token string">"3"</span></code></pre></div> <p>Example implementation of the update support, could look like:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">const</span> <span class="token function-variable function">route</span> <span class="token operator">=</span> <span class="token punctuation">(</span>router<span class="token operator">:</span> Router<span class="token punctuation">)</span> <span class="token operator">=></span> router<span class="token punctuation">.</span><span class="token function">post</span><span class="token punctuation">(</span> <span class="token string">'/clients/:clientId/shopping-carts/:shoppingCartId/products'</span><span class="token punctuation">,</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token punctuation">(</span>request<span class="token operator">:</span> Request<span class="token punctuation">,</span> response<span class="token operator">:</span> Response<span class="token punctuation">,</span> next<span class="token operator">:</span> NextFunction<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> command <span class="token operator">=</span> <span class="token function">mapRequestToCommand</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> streamName <span class="token operator">=</span> <span class="token function">getShoppingCartStreamName</span><span class="token punctuation">(</span> command<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token function">getAndUpdate</span><span class="token punctuation">(</span> addProductItemToShoppingCart<span class="token punctuation">,</span> streamName<span class="token punctuation">,</span> command <span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token string">'ETag'</span><span class="token punctuation">,</span> <span class="token function">toWeakETag</span><span class="token punctuation">(</span>result<span class="token punctuation">.</span>nextExpectedRevision<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> response<span class="token punctuation">.</span><span class="token function">sendStatus</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span>error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span><span class="token punctuation">(</span>error<span class="token punctuation">.</span>type <span class="token operator">===</span> ErrorType<span class="token punctuation">.</span><span class="token constant">WRONG_EXPECTED_VERSION</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token function">next</span><span class="token punctuation">(</span><span class="token punctuation">{</span> status<span class="token operator">:</span> <span class="token number">412</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">next</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">function</span> <span class="token function">mapRequestToCommand</span><span class="token punctuation">(</span>request<span class="token operator">:</span> Request<span class="token punctuation">)</span><span class="token operator">:</span> AddProductItemToShoppingCart <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> <span class="token operator">!</span><span class="token function">isNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">isNotEmptyString</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>productId<span class="token punctuation">)</span> <span class="token operator">||</span> <span class="token operator">!</span><span class="token function">isPositiveNumber</span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>quantity<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">'INVALID_REQUEST'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> expectedRevision <span class="token operator">=</span> <span class="token function">getWeakETagValueFromIfMatch</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> request<span class="token punctuation">.</span>params<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> <span class="token punctuation">{</span> productId<span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>productId<span class="token punctuation">,</span> quantity<span class="token operator">:</span> request<span class="token punctuation">.</span>body<span class="token punctuation">.</span>quantity<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> metadata<span class="token operator">:</span> <span class="token punctuation">{</span> $expectedRevision<span class="token operator">:</span> expectedRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>I’m using EventStoreDB as an example, but the logic will be the same for most databases and frameworks. Supporting optimistic concurrency is a must for a mature production-grade system.</p> <p>EventStoreDB append even will make sure that provided revision matches with the one from the database. It will also return a new expected one as the result of the operation. We can use it to produce the new <em>ETag</em> header value in response. If the revisions don’t match, it will throw <em>WrongExpectedVersionError</em>.</p> <p><em>ETag</em> header is mapped from the header and passed through command metadata. The <em>getAndUpdate</em> method takes command, command handler and stream name. The first step is to retrieve state from events (read more on that in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing/">How to get the current entity state from events?</a>). Both events and command are passed to the command handler, where the business logic is run. As a result, we’re getting a new event that we can store in EventStoreDB. We’re doing that together with the expected revision to perform an optimistic concurrency check. See details below:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">addProductItemToShoppingCart</span><span class="token punctuation">(</span> events<span class="token operator">:</span> ShoppingCartEvent<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> command<span class="token operator">:</span> AddProductItemToShoppingCart <span class="token punctuation">)</span><span class="token operator">:</span> ProductItemAddedToShoppingCart <span class="token punctuation">{</span> <span class="token keyword">const</span> shoppingCart <span class="token operator">=</span> <span class="token generic-function"><span class="token function">aggregateStream</span><span class="token generic class-name"><span class="token operator">&lt;</span> ShoppingCart<span class="token punctuation">,</span> ShoppingCartEvent <span class="token operator">></span></span></span><span class="token punctuation">(</span>events<span class="token punctuation">,</span> when<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>shoppingCart<span class="token punctuation">.</span>status <span class="token operator">&amp;</span> ShoppingCartStatus<span class="token punctuation">.</span>Closed<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">'SHOPPING_CARD_CLOSED'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">'product-item-added-to-shopping-cart'</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> shoppingCartId<span class="token operator">:</span> command<span class="token punctuation">.</span>data<span class="token punctuation">.</span>shoppingCartId<span class="token punctuation">,</span> productItem<span class="token operator">:</span> command<span class="token punctuation">.</span>data<span class="token punctuation">.</span>productItem<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">getAndUpdate</span><span class="token generic class-name"><span class="token operator">&lt;</span> CommandType <span class="token keyword">extends</span> Command<span class="token punctuation">,</span> StreamEventType <span class="token keyword">extends</span> Event <span class="token operator">></span></span></span><span class="token punctuation">(</span> <span class="token function-variable function">handle</span><span class="token operator">:</span> <span class="token punctuation">(</span> currentEvents<span class="token operator">:</span> StreamEventType<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> command<span class="token operator">:</span> CommandType <span class="token punctuation">)</span> <span class="token operator">=></span> StreamEventType<span class="token punctuation">,</span> streamName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> command<span class="token operator">:</span> CommandType <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>AppendResult<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> eventStore <span class="token operator">=</span> <span class="token function">getEventStore</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> currentEvents <span class="token operator">=</span> <span class="token keyword">await</span> <span class="token generic-function"><span class="token function">readFromStream</span><span class="token generic class-name"><span class="token operator">&lt;</span>StreamEventType<span class="token operator">></span></span></span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> streamName<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> newEvent <span class="token operator">=</span> <span class="token function">handle</span><span class="token punctuation">(</span>currentEvents<span class="token punctuation">,</span> command<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> expectedRevision <span class="token operator">=</span> command<span class="token punctuation">.</span>metadata<span class="token operator">?.</span>$expectedRevision <span class="token operator">?</span> <span class="token function">BigInt</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>metadata<span class="token operator">?.</span>$expectedRevision<span class="token punctuation">)</span> <span class="token operator">:</span> <span class="token keyword">undefined</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">appendToStream</span><span class="token punctuation">(</span>eventStore<span class="token punctuation">,</span> streamName<span class="token punctuation">,</span> <span class="token punctuation">[</span>newEvent<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> expectedRevision<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">async</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">readFromStream</span><span class="token generic class-name"><span class="token operator">&lt;</span>StreamEventType <span class="token keyword">extends</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span> eventStore<span class="token operator">:</span> EventStoreDBClient<span class="token punctuation">,</span> streamName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> ReadStreamOptions <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>StreamEventType<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> events<span class="token operator">:</span> StreamEventType<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> resolvedEvent <span class="token keyword">of</span> eventStore<span class="token punctuation">.</span><span class="token function">readStream</span><span class="token punctuation">(</span> streamName<span class="token punctuation">,</span> options <span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>resolvedEvent<span class="token punctuation">.</span>event <span class="token operator">===</span> <span class="token keyword">undefined</span><span class="token punctuation">)</span> <span class="token keyword">continue</span><span class="token punctuation">;</span> events<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>StreamEventType<span class="token operator">></span><span class="token punctuation">{</span> type<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">!</span><span class="token punctuation">.</span>type<span class="token punctuation">,</span> data<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">!</span><span class="token punctuation">.</span>data<span class="token punctuation">,</span> metadata<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">?.</span>metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> events<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">appendToStream</span><span class="token generic class-name"><span class="token operator">&lt;</span>StreamEventType <span class="token keyword">extends</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span> client<span class="token operator">:</span> EventStoreDBClient<span class="token punctuation">,</span> streamName<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> events<span class="token operator">:</span> StreamEventType<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> options<span class="token operator">?</span><span class="token operator">:</span> AppendToStreamOptions <span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator">&lt;</span>AppendResult<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">const</span> jsonEvents<span class="token operator">:</span> EventData<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token punctuation">(</span>event<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">jsonEvent</span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> event<span class="token punctuation">.</span>type<span class="token punctuation">,</span> data<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">,</span> metadata<span class="token operator">:</span> event<span class="token punctuation">.</span>metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> client<span class="token punctuation">.</span><span class="token function">appendToStream</span><span class="token punctuation">(</span>streamName<span class="token punctuation">,</span> jsonEvents<span class="token punctuation">,</span> options<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>See the full sample in: <a href="https://github.com/oskardudycz/EventSourcing.NodeJS/pull/14">https://github.com/oskardudycz/EventSourcing.NodeJS/pull/14</a>.</p> <p>Optimistic concurrency also allows you to simplify logic and, especially in non-relational databases, obtain strong guarantees without using such heavy tools as unique keys, foreign keys, etc. We can skip those checks if we know that we are making business decisions based on the latest state of our data.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Tell, don't ask! Or, how to keep an eye on boiling milk]]>https://event-driven.io/en/tell_dont_ask_how_to_keep_an_eye_on_boiling_milk/https://event-driven.io/en/tell_dont_ask_how_to_keep_an_eye_on_boiling_milk/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2218a000a4cef83a6845be0b158cc670/d2429/2021-10-27-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9ADs5PU1MT0xJSkE+QVFPVFhWXE1LUG1sc3BudWZjZ2FdYFFNT0tJTUlHTV5cY3FxeXV2gHd3gG9vdmlobgAHBwgoKS8aGRsREBMpKjIzMzkxMThnZ3FYV19CP0NBPD47OTtLS1JUVFtiY2tZWWNRU19maHNTVFxRUFYAICAkQkJJPj5FKysyJScwJygwPz9JZGVyZ2hyi4uUp6iusrO4vsDDwsTJvb/DrK6yjI+ZYWJtREVOUlJaAEtLU3h3fXFxd2ttdWtrcVJRVlVVXm9xfbm9x+Hl69fa3tHT1cjIyMXExMPCwsHBv8zNzpCRmSIfIjs4PAAjISUxMzxIS1VMTFVFRUpBQklrbXmVmKbLy8rW2NvV2d3MztHKzM7AwMC/v769vby/wL6WlpogGho4NjcAMjA0NjY8NDZANTdCLS85Jik1T1VnanOHkJSdw8XKzM3R0tTX1NfbyMnLwMDAvL7AmJWXPDg5HxwdMjAzAEtLUT4/RRUVGQ0MDxcWHDxAT3mDmZ2txDVBWKayyIySn4KAg5GPjY+Nj2ZhYFtUUUM5MyckJTIwMjY1OABBPz86OTkSEBICAAAaGB0/Q1KWpLutvdVldY6KnLlndpIbGB8sKShZU1UsIh4eFxIcGhYiIB40MS4xMTMALSwtKykoFxYXBwUGDQsMREtbpLPOrbzUlaS+boKlTmB/HyAkHyAgOzs8HSAbCAkGCgUFJiMhMi8sLCwtAAcGBQYFBQ8PDwsKCgwKChwcImJrgZSkv29+moqXrjs5SzIgJy0eHi0YGzAJDzIFCQsAAB0eHzg2OCcnKgAFBAMGBQMHBwcJCAkHBgUPEBQSFRwfJDIqMUAvOlA2N0geCA8rCBAyBw8tAwsRBAYBBAUjIiM4ODgkJCQUm+II6+q+hwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/2218a000a4cef83a6845be0b158cc670/a331c/2021-10-27-cover.png" srcset="/static/2218a000a4cef83a6845be0b158cc670/36ca5/2021-10-27-cover.png 200w, /static/2218a000a4cef83a6845be0b158cc670/a3397/2021-10-27-cover.png 400w, /static/2218a000a4cef83a6845be0b158cc670/a331c/2021-10-27-cover.png 800w, /static/2218a000a4cef83a6845be0b158cc670/d2429/2021-10-27-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Some time ago, I thought about writing a cookbook for guys, a set of essential tips on surviving in the kitchen. I thought of starting with brewing coffee and making tea, then going to advanced one like how to heat the milk so that it does not boil over and make a soft-boiled egg.</p> <p>I don’t know about you, but I must be extraordinarily patient and vigilant in these matters. Let’s take boiling milk, for instance. It is a very malicious individual. Before heating, you should obtain matches. Don’t you have a gas stove at home? Never mind, these matches are not for lighting gas. They are from holding drooping eyelids. Milk usually boils for a few minutes. It is challenging to have enough self-control not to blink. Because when you blink, then… The milk has boiled over!</p> <p>You look once - cool. You put your finger in - still lukewarm. Suddenly you sneeze, your eyes close and you screech! The pot and stove are whole in boiled milk.</p> <p>It’s the same with a soft-boiled egg. The recipe is simple. Pour water into a pot and put eggs in it. Wait for the water to boil. When the water boils, cook the eggs thoroughly: S - 3.5 minutes, M - 4 minutes, L - 5.5 minutes. And here there is the issue that somehow this egg never fits into S, M or L. Moment passes, and we already have an egg too hard for being soft and too soft for being hard.</p> <p>As I have this book ready, the second volume will be about programmers and their IFs waiting and verifying whether a record can be saved or not. Surely you know this code:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">if</span> <span class="token punctuation">(</span>invoiceService<span class="token punctuation">.</span><span class="token function">Exists</span><span class="token punctuation">(</span>invoiceNumber<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> invoiceService<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">Invoice</span> <span class="token punctuation">{</span> InvoiceNumber <span class="token operator">=</span> invoiceNumber <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>When I see such a code, I can recite from memory how the dialogue will proceed:</p> <ul> <li><strong>Me</strong>: Why do you need this if?</li> <li><em><strong>Interlocutor:</strong> Because an invoice with the same number cannot be added, what do you not understand?</em></li> <li><strong>Me:</strong> And this if will provide you with this?</li> <li><em><strong>Interlocutor:</strong> Yeah.</em></li> <li><strong>Me:</strong> What if someone adds an invoice with the same number while you’re waiting for the check result?</li> <li><em><strong>Interlocutor:</strong> This is not gonna happen.</em></li> <li><strong>Me:</strong> Well, how not? After all, it can happen technically.</li> <li><em><strong>Interlocutor:</strong> Okay, but very rarely.</em></li> </ul> <p>Curtain.</p> <p>It often turns out that there is a unique constrain on the database that will guard uniqueness constraint. The IF is added either <em>“just in case”</em> or <em>“just to have a chance for nasty bug”</em>.</p> <p>Worse as a developer despises adding database constraints. Who defines keys or indexes these days? SQL in the 21st century? Ugh!</p> <p>Then we are dealing with mild schizophrenia. Because business says that it is required, the programmer thinks so too, but in fact, it’s only a little, as much as they are comfortable with.</p> <p>When writing such a code, we behave as when heating milk. This time you will succeed! I won’t blink now, and nothing terrible will happen. I’ll even try not to breathe! Milk cannot boil over in a split second.</p> <p>However, the problem is not with the “constraints” themselves. It also applies to shaping the API itself. I think you’ve also seen situations where we had to call <em>“Validate”</em>, <em>“CheckPermissions”</em>, <em>“Exist”</em> while writing the code. Check if the milk has boiled over yet, put your finger in and check the temperature. If it’s M, then cook 4m. If L then 5.5m.</p> <p>The more we have to remember to use our API correctly, the more likely we will forget about some steps. According to Murphy rule, if people can forget to call the <em>Validate</em> method before doing Save, then for sure, they will.</p> <p><strong>That is why our API should be built according to the <em>“tell, don’t ask”</em> principle.</strong></p> <p>Our API should let us tell what we want to achieve and handle necessary checks internally. We should not be asking on each request, just in case, _“can I do it?“. In our example, the <em>Add</em> method should verify if there is no other invoice with the same number. We should not require to remember to call <em>Exists</em> before that.</p> <p>By doing this, we gain:</p> <ul> <li>more straightforward and predictable API, especially if we map, for example, a database exception to some more readable domain error and return it as a result.</li> <li>reliability. If we encapsulate the check, we don’t need to be afraid that someone forgets about calling it.</li> <li>more efficient API - if you do not always have to do an additional query <em>“and is there such a record”</em>, we gain better performance. We don’t need to do additional calls, open new connections or have hanging threads causing deadlocks.</li> </ul> <p>Win-win!</p> <p>If we have tools like databases with unique constraints that can make our code more performant, reliable, and less clunky, we should take advantage of that. Some people say that <em>“Having the check, in code is more explicit and you see the business rules”</em>. That’s true, but I take the tradeoff in 99% time. I prefer to have a simpler, more reliable code that works as it should than code that is more explicit but wrong.</p> <p>So the next time you do such a check, remind yourself of boiling milk and <strong>tell, don’t ask!</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. check also <a href="/en/optimistic_concurrency_for_pessimistic_times/">Optimistic concurrency for pessimistic times</a>, it should help you on telling instead of asking.</p> <p>Read also <a href="/en/what_does_mr_bean_opening_the_car_have_to_do_with_programming/">What does Mr Bean opening the car have to do with programming?</a> to continue hunt on redundant abstractions.</p><![CDATA[What does Mr Bean opening the car have to do with programming?]]>https://event-driven.io/en/what_does_mr_bean_opening_the_car_have_to_do_with_programming/https://event-driven.io/en/what_does_mr_bean_opening_the_car_have_to_do_with_programming/<p>Before reading the article, please watch the video below:</p> <p><a href="https://www.youtube.com/watch?v=GOd7oj1AT00"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAACRElEQVQoz32Sy08TARCH2+0uLbRsH7S12+1zaWsDBYpISW2QhlLQhpcHXrWI4SGJRKwYEjGQKCFpjI+LMSTixQT14glPmnjg6sGLF/+bz1AVhVQPc5iZ5JvfzG90Op2Ov0MQ9DSY6lCcMjHVzvhAitLIBa6PpCj0tpGM+VGcVvJ93QRUN6LBwCnGn0Q0CJjrjbgdFlrDbnrbfdybznLWbSefbmFhrIeAS8ZqqScc8OBTXKdhJ4EWs4kzTpmAx0Em4efqpXM8mM6xlEmwVS4yN5qiI+oh7HVgMAjo9fr/Aw2CvjrdaTUT99vZXBhje2WOr4ef+H74llSbhua1M9AVOT7PEbgm8Giaw2HD7ZRJxMPMjOZ4sVWmcmOGxVKR+fFBLmeTKJ5G+pIhvIoNU71Eo8V0WulvMwTsdlt15eLEEOXlEiMDF5ElgWZNI5NOc/B+jyePVgkqNvL9bbTEFYx1YlVpDaCegOrhSqGPxWKBbLqDiNJEzK8ynO8n7HURCao0B710xoO0R1U0n4uAakcL2Wor1EI+ZieH2Xu+w/5uhVxnHKu5gUQ0zM7aPOu35ykvz7C+MstSaZR0VwshvwuvItc2xWZtpLuzlbWVa1Q2bxEPeI57B6+f8e3LRz5/eM27V4/Z3rhJ+nzilzGG2kBJFFEVN4PZHmYnhpAksVp3N9nY393hzcsKTx/eYWN1jonxfpo1PyajhCj+A/jzFxvoaI2S6+3CWCdVa8lEhPt3FylNFigvTTE1liHTE8Mqy5hM0om3+QE+sjvYP5SiqwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="Mr Bean" title="Mr Bean" src="/static/8c24d42ce71cb6cd0b9d8ee6da1ca2b8/a331c/2021-10-20-play.png" srcset="/static/8c24d42ce71cb6cd0b9d8ee6da1ca2b8/36ca5/2021-10-20-play.png 200w, /static/8c24d42ce71cb6cd0b9d8ee6da1ca2b8/a3397/2021-10-20-play.png 400w, /static/8c24d42ce71cb6cd0b9d8ee6da1ca2b8/a331c/2021-10-20-play.png 800w, /static/8c24d42ce71cb6cd0b9d8ee6da1ca2b8/d2429/2021-10-20-play.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p>Mr Bean wants to add a new business feature to the project. First, the entity class, then the repository (and registers its generic version in Dependency Injection container), then the application service, request validator, mapping from request classes to entities. He is about to run it, but he reminds himself that it won’t work because he forgot to add a new controller. Time goes quickly, the controller is here, he’s firing it up and boom! It does not work because he forgot to register something else in the IoC container. Once that is done, only unit tests are left for each of the added classes, some integration tests and finally, endpoint adding a dictionary entity with two fields works.</p> <p>Boy, that escalated quickly!</p> <p>The more advanced code structures were used, the more Mr Bean can feel fulfilled. Generic factories, reflection-based aspect programming, fancy providers integrated with generators, that’s how serious enterprise architecture looks like.</p> <p>I used to think so once. Once, during the team building, my colleague came to me and said, <em>“Oskar, you know what, it took me about three months to understand our architecture. In the beginning I was totally lost, but now, once I understood it, I think it’s great!”</em>. At first, I felt proud that I had designed such sophisticated architecture. I thought that has to be good if it requires a lot of time for understanding, but then it pays off. Then I realised that’s not complex. It’s just complicated. There was a lot of magical, generic code that could do anything, even brew coffee. Absolute champion was a class with 14 generic parameters to fill in. Well, we were making a serious business application after all!</p> <p>Is complex or complicated really that cool? Is this whole ceremony really needed? I used to think that. Now I think the best code is the one that when we see, we’ll say, <em>“Hell, how easy it is, why didn’t I come up with that”</em>. Contrary to appearance, such code requires a lot of work. Designing upfront, a few iterations and cutting every redundant abstraction. Sometimes these are small things, such as adequately placed IFs. Sometimes these are slightly larger things, such as learning about new patterns, paradigms - e.g. functional programming, architectural patterns such as Event Sourcing, CQRS. Sometimes sideways, jumping on the neighbour’s lawn in a different language or technology gives us new insights and ideas.</p> <p>I am not a dogmatist. I come from the .NET world - this is my lair, which I orbit, but I like to learn other technologies. There are times where I’m writing more in other than C# languages, e.g. in the last project, I coded much more in TypeScript and Node.JS. Working with different languages ​​and programming environments allows you to look at your code from a different angle. For example, <a href="/en/partial_typescript">I used to hate JS</a>. I was calling it <em>“shitty language”</em>. Now, I think I just didn’t understand it and couldn’t write in it well at that time. When I changed my thinking, it made a lot of sense, and its dynamics, simplicity, and functional flair taught me a lot.</p> <p>So, for example, when I come back to Visual Studio and .NET, I think, <em>“why is it so heavy?!”</em>. That’s why I’m happy about the improvements in C#9 (check also <a href="/en/notes_about_csharp_records_and_nullable_reference_types">Notes about C# records and Nullable Reference Types</a>), I’m glad that the main method is not required, or the ability to do endpoints without a controller. The fewer ceremonies, the better.</p> <p>I also often come across the statement, <em>“a programmer should know this and remember it”</em>. The more a programmer has to know and remember, the more likely they will forget and make stupid mistakes. In my opinion, such necessary routine repetition causes the programmer to stop thinking and start copying and pasting, which is the best way to get stupid bugs that are hard to detect.</p> <p>Therefore, homework for you:</p> <ul> <li>think over how many classes and layers the request goes through until it is saved in the database,</li> <li>how many generic classes do you have, especially with a few parameters, and what problems do you have with it,</li> <li>do you know why composition is better than inheritance?</li> </ul> <p>Consider whether it must be so and whether these structures do not resemble, by chance, Mr Bean’s car opening. Feel free to send your conclusions in the comments.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. if you like that article, check also <a href="/en/generic_does_not_mean_simple">Generic does not mean Simple</a>!</p><![CDATA[What does a construction failure have to do with our authorities?]]>https://event-driven.io/en/what_does_a_construction_failure_have_to_do_with_our_authorities/https://event-driven.io/en/what_does_a_construction_failure_have_to_do_with_our_authorities/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f748655e118b2b9d5ce6b7dd6f9f4f85/d2429/2021-10-13-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6cAAAOnAEHlFPdAAAChUlEQVQozyXOTU/acBwA4J+FFmgp/5RCX2AlFVgUgxTCYZNA6KZMooPSiAoaZ8bLjTmEbALRePAygyaLBEl0lyWePGji1YsnT8YDBxMTE+9+iiXu+QQPPDw8nJ2dtVqtZrNZr9cbjUYul8tkMvl8vt1un5yc3Nzc3N7eDofDu7u74+Pj+/v7x8fH6+vr8/NzGA6HOzs73W53f3+/0+mUSqVisbi3t3d0dNTv9w8PDy8uLnq93sHBQblcrtfrp6enl5eXrVarUqnA8/OzruvLy8uDwUDTtJmZmWw22+12Nzc3q9VqKpUqFovT09OFQsHr9SaTyZWVlVgspmlaoVCAl5cXRVE4jotGowghiqJIkpybm/N4PAsLCwghRVF0Xff5fHa7HQDi8Xg6nQaASCQCT09PwWAQXrEsa7VaZVlOp9NjY2Nut1uWZYIgWJYVRZEkSYZhLBaL0WgkCCIUCkGv1xMEAcMwo9EIABRFEQQhSVI0GhUEwWAw0DQ9OjqK4zhFUQzD2O12HMc5jhMEAa6urmRZRgglEgmHwzEyMuJyuXZ3d2u12tTUFAAQBOF0Onmep2mae2V6heM4bG1tIYR4nk8kEqIoAkAgEOh0OvF4XJIkADCZTDzPu1wuk8lks9kQQhiGmc1mkiTB65U5zun3+xFCPp+P5znaRquqGg6HcdyIYRiO4+VyeWlp6f+CpmmDwWAxm0VRAFX9kM3mVlfXJidDPv/bN24pHI4kkyrDMCRJjo8HJMnz6dNsq9W2WmmzhWRZB2W1BSaCmqbDxrfK9naj/r2az2fWvxTfvY98La2pakxRJuY/zy4u5jLZtPox/uPnxvp6YTalLurz7WZt8PvX3z/9f7APp/oT02NdAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/f748655e118b2b9d5ce6b7dd6f9f4f85/a331c/2021-10-13-cover.png" srcset="/static/f748655e118b2b9d5ce6b7dd6f9f4f85/36ca5/2021-10-13-cover.png 200w, /static/f748655e118b2b9d5ce6b7dd6f9f4f85/a3397/2021-10-13-cover.png 400w, /static/f748655e118b2b9d5ce6b7dd6f9f4f85/a331c/2021-10-13-cover.png 800w, /static/f748655e118b2b9d5ce6b7dd6f9f4f85/d2429/2021-10-13-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Through my window, I see the result of good plans but poor execution. Opposite my flat, there is a partially completed construction place. Buildings were supposed to be eye-catching Mediterranean style apartments. Delivery date? Two years ago. Actual? More and more unknown.</p> <p>Some time ago, I heard that using Event Sourcing makes creating Event-Driven Architecture easier. The arguments were correct, that if we’re already publishing events to trigger business workflows, then at some point, we may want to also store events to not lose information. Agreed. However, I also heard that keeping the state as events will simplify things. We’ll have a source of truth with a record of the system behaviour. This will allow, e.g. to confront the results of the operations with the recorded state. I’d agree with that, with one distinction. It’s easier as long as you already know Event Sourcing.</p> <p>Many people in the DDD community claim that the essential is to properly break down the system into autonomous parts called bounded contexts. Once we have it, the rest is secondary and will sort itself out. For sure.</p> <p>Many seasoned programmers speak similarly about new technologies. They claim that they can translate past experience into new technologies. That’s true that by analogy, they can catch the big picture quicker. But isn’t it a bold assumption to say that Win.Forms specialist will learn Angular quickly?</p> <p>The end result may differ a lot from the initial ideas. I saw the plan of those buildings next to me. Now I can see the effects of the execution. Or actually, the lack.</p> <p>I believe that we should carefully acknowledge not only the point of view of our authorities but also their seating point. If we want to find out how to form a wall, do we ask an architect or a foreman? An architect may know the theory, but the practice is what we’re looking for. On the other hand, if you want to know where to put the wall, you prefer the architect to do measurements. At least if you don’t want to have the roof falling to your head.</p> <p>After I had torn a ligament in my knee, I went to two qualified orthopedists. One said I should have surgery and do a reconstruction. The second stated that there is no need for that; rehabilitation should be enough. Guess which one had a specialization in surgery and which in rehabilitation?</p> <p>People usually give us advice from the point where they’re currently standing. They are entitled to a biased view. An architect who rarely does programming will tend to downplay the value of implementation and tactical patterns. Midlevel developers will focus on technicalities instead of the global system impact. The team manager or consultant will emphasize the importance of soft skills (or esoteric techniques known only to them).</p> <p>The truth is that we need all of them. The excellent plan will fall on the bad execution. The best execution for the wrong case will be just a waste of time. We should carefully evaluate the advice considering what we need and what an expert can give us.</p> <p>Therefore, when we’re reading an article, watching a talk, let’s also pay attention to the place where the person is standing. The perspective from there may be much different from where we are right now. That can be good, as it may push us in the right direction. But it may also be misleading, as we accidentally take biases of this person without understanding the tradeoffs. Personally, I prefer to follow not only people from pedestal but also those that are closer to my position. A bit further in the journey, but not too far. That helps me to calibrate my view as those people are more relative to my daily struggles.</p> <p>Polish historical leader <a href="https://en.wikipedia.org/wiki/J%C3%B3zef_Pi%C5%82sudski">Józef Piłsudzki</a> reportedly used to say: <em>“Right is like an ass, everyone has its own”</em>.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Let's talk about positions in event stores]]>https://event-driven.io/en/lets_talk_about_positions_in_event_stores/https://event-driven.io/en/lets_talk_about_positions_in_event_stores/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/1414e366b819e8038f08c7fbcbbe8aaf/d2429/2021-10-06-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6cAAAOnAEHlFPdAAACoUlEQVQozwXB61dZAQAA8Ptt+9LaWdtEURSWyUQyetzjKknEaDQqb3kVpusRkbdcSQslqUR2TqdaWzudTh/3l+33Ax7/Xf95rrefb+tPt8lCsFBNnd1UWo/t5q9G46Fdu7usXteavxsnP0vl9hE4PdH56sUUxOTwOINDA0D1wJiIKLxpl8KlFyqhSN52Xgpf3e5vF0J7lXDjPNoowheX4ftmHDny8SA6+vVLMv5dPwHTi0UBsIOcT3IyMbbROsjnv3E5yac7lmJCvlda8qRgY9R3WnMiu3OGoDcUHB9joBhEDIWA7urs6EV1AhIx2uMgeaw05wZFMocOeFhxmBf1S7NpQyhmXw9Y0jmrPyABZRMOA5vLwtHJOKNmblkGDvS9BaCvUMw30sordxMsuQCr1+BT2+xiQlApCA/SsmpBXSvOlJD5/RA3ZCdBkzg8Du00SXUrEhoZC/D4WO2a0mTWu+EVGUiQiigW3cd8avYQWShERdubs0hMjMTFG066UNhN/IDpRr0nEnro9CHSIA6YYnWzuVgWSGZxcIS+DgikWnR8rY4XsC+EYNmWG4wv0SJuoXdzdnWZxhntmR7FwSsTn4fxIGcYGB/tmYMwYf1YwEWDwK7VZRmSgzVapdUkrx8HGpV1ZJ2PuAVJDxT2ThrV/cEletYqMIqYZhUXsJt7L47ETzXzyQFPvYhzGr7Et3ThgKR8aGudheOpbxdN+P4ymd1SZdIi3eKwb5FR8UhTJqGUMwAkQ/2NKrOUZB4mRvw2qmVtJp9W3507/z7spnLxyg/v/ZW7XHNkke9Bv0LBIzpFlNV5tma8LyImApkotV4GbfpPNhVDJSLpDMxcRNMoe45LhlRGuxOT58KSbFrj8kPuDYFdQUWskzcZWSvEO4ko/wPIV/w4Y/fkDwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/1414e366b819e8038f08c7fbcbbe8aaf/a331c/2021-10-06-cover.png" srcset="/static/1414e366b819e8038f08c7fbcbbe8aaf/36ca5/2021-10-06-cover.png 200w, /static/1414e366b819e8038f08c7fbcbbe8aaf/a3397/2021-10-06-cover.png 400w, /static/1414e366b819e8038f08c7fbcbbe8aaf/a331c/2021-10-06-cover.png 800w, /static/1414e366b819e8038f08c7fbcbbe8aaf/d2429/2021-10-06-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Discussions about positions can be awkward, to say the least… It is also the case with positions in Event Sourcing.</strong> Each event store implementation has its own nomenclature and specificity.</p> <p><strong>First, let’s find out what is common.</strong> Each event store should have two main structures related to the events:</p> <ul> <li><em>append-only log</em> - a structure where all events are added. The name comes from the fact that events can only be added at the end. Thanks to this, we keep the chronology and sequence of events.</li> <li><em>stream</em> - it is a sequence of events. Those events are correlated by stream identifier, usually representing what facts registered for a specific entity.</li> </ul> <p>Both entities can be considered as FIFO queues (First In, First Out). We always append an event to the end.</p> <p>Both the append-only log and the individual streams have positions. They represent the specific relative place in the chronology. Using a position, we can walk the events in order of appearance and read history from them.</p> <p>How are they implemented?</p> <p><strong>In Marten, the append-only log is one table</strong>. The primary key is the sequence field generated by a Postgres sequence with a monotonic number. There may be gaps when an event is not added for some reason (e.g. a conflict or transient error). The events stream is a virtual structure. It is simply a collection of events from the append-only log. It can be selected by the event identifier. The events within the stream have a number called version, which is also a monotonic number, but without gaps. This number can also be used to handle optimistic concurrency. Read more <a href="/en/optimistic_concurrency_for_pessimistic_times/">“Optimistic concurrency for pessimistic times”</a> article.</p> <p><strong>In EventStoreDB, the design is similar.</strong></p> <p>The append-only log is called the <em>$all stream</em>. It is a specific stream representing all events stored in the database. It is a logical representation of the memory (disk) position where the event is located. The position is monotonic, and may have gaps. To be precise, it is actually composed of two such pointers: a <em>commit</em> and a <em>prepare</em>. They are remainings from when transactions were supported in EventStoreDB (it is to be unified into one number in upcoming versions). For now, if you’re not using transactions, you can treat the commit position as the single relevant one.</p> <p><strong>The stream version is an automatically incremented number.</strong> It is called stream position. Similarly, on its basis, EventStoreDB allows you to use it for optimistic concurrency. Then it is called stream revision.</p> <p>Interestingly, in EventStoreDB, the physical structures are streams. The <em>$all stream</em> is the secondary structure.</p> <p>If you subscribe to <em>$all</em>, you will receive events from multiple streams respecting the global order in which they occur. You will need to save the position as the checkpoint of the last processed event. The position can also be used for deduplication and idempotency. You can check if an event was already processed.</p> <p><strong>It is similar also in Kafka.</strong> Although it is not a proper tool for Event Sourcing, it is similar in that the notion of an “append-only log” lies at its core. There, the logical structure is <em>topic</em>, split into a set of physical <em>partitions</em>. The position is named <em>commit offset</em>. This is the entry where the event has been saved on the partition.</p> <p>Here’s a picture of what it looks like in the Event Store!</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b89a3290f9a1582c82c212aa52b5cd01/3ee92/esdbpos.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 37.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAIABQDASIAAhEBAxEB/8QAFwABAAMAAAAAAAAAAAAAAAAAAAECBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAdyQsD//xAAVEAEBAAAAAAAAAAAAAAAAAAAQQf/aAAgBAQABBQKH/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFBABAAAAAAAAAAAAAAAAAAAAEP/aAAgBAQAGPwJ//8QAGBAAAgMAAAAAAAAAAAAAAAAAAAEQcYH/2gAIAQEAAT8hVjHH/9oADAMBAAIAAwAAABAAD//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8QP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8QP//EABkQAQADAQEAAAAAAAAAAAAAAAEAEUFhUf/aAAgBAQABPxAKK5R2F70gPrP/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="ESDB Positions" title="ESDB Positions" src="/static/b89a3290f9a1582c82c212aa52b5cd01/c60e9/esdbpos.jpg" srcset="/static/b89a3290f9a1582c82c212aa52b5cd01/37402/esdbpos.jpg 200w, /static/b89a3290f9a1582c82c212aa52b5cd01/4cda9/esdbpos.jpg 400w, /static/b89a3290f9a1582c82c212aa52b5cd01/c60e9/esdbpos.jpg 800w, /static/b89a3290f9a1582c82c212aa52b5cd01/6c738/esdbpos.jpg 1200w, /static/b89a3290f9a1582c82c212aa52b5cd01/3ee92/esdbpos.jpg 1221w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>As Phil Karlton said: <em>“There are only two hard things in Computer Science: cache invalidation and naming things.”</em> All of the names for the position seems to have their issues, but I hope that this article brought you closer to understanding what they’re all about.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. if you liked this article, check also:</p> <ul> <li><a href="/en/relational_databases_are_event_stores/">What if I told you that Relational Databases are in fact Event Stores?</a>,</li> <li><a href="/en/integrating_Marten/">Integrating Marten with other systems</a>,</li> <li><a href="/en/persistent_vs_catch_up_eventstoredb_subscriptions_in_action/">Persistent vs catch-up, EventStoreDB subscriptions in action</a>,</li> <li><a href="/en/event_stores_are_key_value_stores">Event stores are key-value stores, and why that matters</a>,</li> <li><a href="/en/lets_build_event_store_in_one_hour/">Let’s build event store in one hour!</a>.</li> </ul><![CDATA[How to build event-driven projections with Entity Framework]]>https://event-driven.io/en/how_to_do_events_projections_with_entity_framework/https://event-driven.io/en/how_to_do_events_projections_with_entity_framework/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/709338a9ac1ce6db5584f2cc48920479/d2429/2021-09-29-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6cAAAOnAEHlFPdAAABxElEQVQoz5WRz2sTURDHZ+a9XfOSdd++xyYbt6nJi0KbhUraakFBUPCkpGjAUw+VQoOK4kWSSgU3VRCMKPijlmihHhSLHlQ8CBIvguBd/Xtk3aD2Yun3MIcZPszM9ws5D2bOYqNDZ2JcHrD2U7rfp5ECAgAx2EaIsP8QNbuw9E7f6V9+8n36wQ9xpUciN5xuI69AzRWIN490bs7fftnov79696eYOAF+GYn/uybRFlK4UByDC+uysz517jFdX529tXF6ecAW1tilF1yX8H/7j7eoGePSm9K1j9nWc+i+nrkxkOc3cGHVmuvxrBxyRFStVpVSW+CpBl1cK8Zvxx99m+59Nd3P/mzb3ncQxw5DqZacqpQyxtRqNcuyKpVKEAR/4b0HcPGh6rwajT+Ye18mWs8s4SZ9jrYFGZt2ZUgAQBAEQghEjKJIa51aADkPRusweYq1N0dWPpXGjyWkzZlnqYJdLFhFxXUWc9rXYRgKIXzfj6KIsTTG345kHDg6j5MnWZotR+6w3R7XmuclT/7M531jjJRSKVWv140xQxgprckhqbEE5DLpMhlYewRl0wcdx2GMua5bLpfDMLRt+0+ICUYEO9Iv/L5KrQadlr0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/709338a9ac1ce6db5584f2cc48920479/a331c/2021-09-29-cover.png" srcset="/static/709338a9ac1ce6db5584f2cc48920479/36ca5/2021-09-29-cover.png 200w, /static/709338a9ac1ce6db5584f2cc48920479/a3397/2021-09-29-cover.png 400w, /static/709338a9ac1ce6db5584f2cc48920479/a331c/2021-09-29-cover.png 800w, /static/709338a9ac1ce6db5584f2cc48920479/d2429/2021-09-29-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Let’s assume that you’re building an Event-Driven system. You may be using Event Sourcing or microservices integrated with message bus or monolith with modules triggering each other by events.</p> <p>In such systems, meaningful business operations end up with related events. Those events might be stored (in Event Sourcing) or just published. They can trigger other business workflows or be used to build read models. We’re going to focus today on the latter.</p> <p>Read models typically are used for user queries. You get the most out of them if you design them to handle a specific query. In most of the systems, reads are much more frequent than writes. Thus, it’s usually worth selecting slower writes and faster queries when you have to do the tradeoff. You can also use them to build the local interpretation of the external state from other modules. The patterns that I describe below apply to both approaches.</p> <p>Let’s say that we’re using an in-memory event bus defined by such interface:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IEventBus</span> <span class="token punctuation">{</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">Publish</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">TEvent</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token return-type class-name">Task</span> <span class="token function">Publish</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>See sample implementations:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core/Events/EventBus.cs">MediatR</a>,</li> <li><a href="https://github.com/EventStore/samples/blob/main/CQRS_Flow/.NET/Core/Core/Events/EventBus.cs">Vanilla .NET</a>.</li> </ul> <p>Each event published to it will be handled by the event handler defined by:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span><span class="token keyword">in</span> TEvent<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">TEvent</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Event bus can:</p> <ul> <li>be called explicitly in code,</li> <li>get event forwarded from the <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core.EventStoreDB/Subscriptions/SubscribeToAllBackgroundWorker.cs#L124">EventStoreDB subscription</a>,</li> <li>get event forwarded from the <a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Core.Streaming.Kafka/Consumers/KafkaConsumer.cs#L86">Kafka topic consumer</a>.</li> </ul> <p>The critical part of the event bus call from hosted service is to ensure that event bus event handlers are resolved in the new scope. That makes sure that services that require scoping are correctly resolved and disposed.</p> <p>Projection is an interpretation of the sequence of the events. Using <a href="https://medium.com/@zaid.naom/exploring-folds-a-powerful-pattern-of-functional-programming-3036974205c8">left fold approach</a>, we can apply events one by one to the state (read more in <a href="/en/how_to_get_the_current_entity_state_in_event_sourcing">“How to get the current entity state from events?”</a>).</p> <p>For the following set of events representing the ECommerce shopping cart lifecycle:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartInitialized</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ClientId<span class="token punctuation">,</span> <span class="token class-name">ShoppingCartStatus</span> ShoppingCartStatus <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemAddedToShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductItemRemovedFromShoppingCart</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartConfirmed</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> ShoppingCartId<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> ConfirmedAt <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We could define a projection for the list view as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ShoppingCartShortInfo</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> TotalItemsCount <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> TotalPrice <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Version <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartShortInfoProjection</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartShortInfo</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartInitialized</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> shoppingCartStatus<span class="token punctuation">)</span> <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartShortInfo</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> shoppingCartId<span class="token punctuation">,</span> ClientId <span class="token operator">=</span> clientId<span class="token punctuation">,</span> TotalItemsCount <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">,</span> Status <span class="token operator">=</span> shoppingCartStatus<span class="token punctuation">,</span> Version <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartShortInfo</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> view<span class="token punctuation">.</span>Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">;</span> view<span class="token punctuation">.</span>Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAddedToShoppingCart</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartShortInfo</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> view<span class="token punctuation">.</span>TotalItemsCount <span class="token operator">+=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> view<span class="token punctuation">.</span>TotalPrice <span class="token operator">+=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>TotalPrice<span class="token punctuation">;</span> view<span class="token punctuation">.</span>Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemovedFromShoppingCart</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartShortInfo</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> view<span class="token punctuation">.</span>TotalItemsCount <span class="token operator">-=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> view<span class="token punctuation">.</span>TotalPrice <span class="token operator">-=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>TotalPrice<span class="token punctuation">;</span> view<span class="token punctuation">.</span>Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see, projection can be defined by a set of functions. They may either take:</p> <ul> <li>an event as a parameter and return a new read model instance,</li> <li>an event and read model instance and apply the new state to it.</li> </ul> <p>Defining it as functions without reference to any infrastructure code makes testing by unit or integration tests easy. They’re also cleaner, readable and, thanks to that, easier to maintain.</p> <p>The more advanced, Shopping Cart details projection can be represented as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartDetailsProjection</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ShoppingCartDetails</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartInitialized</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>shoppingCartId<span class="token punctuation">,</span> clientId<span class="token punctuation">,</span> shoppingCartStatus<span class="token punctuation">)</span> <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartDetails</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> shoppingCartId<span class="token punctuation">,</span> ClientId <span class="token operator">=</span> clientId<span class="token punctuation">,</span> Status <span class="token operator">=</span> shoppingCartStatus<span class="token punctuation">,</span> Version <span class="token operator">=</span> <span class="token number">0</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartDetails</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> view<span class="token punctuation">.</span>Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">;</span> view<span class="token punctuation">.</span>Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ProductItemAddedToShoppingCart</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartDetails</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> view<span class="token punctuation">.</span>ProductItems <span class="token punctuation">.</span><span class="token function">FirstOrDefault</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> view<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">ShoppingCartDetailsProductItem</span> <span class="token punctuation">{</span> ProductId <span class="token operator">=</span> productItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> Quantity <span class="token operator">=</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">,</span> UnitPrice <span class="token operator">=</span> productItem<span class="token punctuation">.</span>UnitPrice <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">+=</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> view<span class="token punctuation">.</span>Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">ProductItemRemovedFromShoppingCart</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">ShoppingCartDetails</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> view<span class="token punctuation">.</span>ProductItems <span class="token punctuation">.</span><span class="token function">Single</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>ProductId <span class="token operator">==</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">.</span>ProductId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">==</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">)</span> <span class="token punctuation">{</span> view<span class="token punctuation">.</span>ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> existingProductItem<span class="token punctuation">.</span>Quantity <span class="token operator">-=</span> productItem<span class="token punctuation">.</span>Quantity<span class="token punctuation">;</span> <span class="token punctuation">}</span> view<span class="token punctuation">.</span>Version<span class="token operator">++</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Above projection contains nested objects and more sophisticated logic on updating the state. However, it’s still clean and self-explanatory. In general, projections shouldn’t contain a lot of business logic. Its place is in the write model.</p> <p>OK, but how to use them to update the data? Let’s use Entity Framework as an example of how to deal with that. In general, the relational model is not ideal for the read models. By its nature, normalisation brings additional overhead with table joins, etc. Nevertheless, relational databases are the most popular storage, and ORMs are the most popular way to use them in an object-oriented world. We should also use the way that helps us to integrate with our other technology stack or our team skills.</p> <p>For the majority of cases, we can use two main patterns:</p> <ul> <li>create, that can be described by code as:</li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> view <span class="token operator">=</span> <span class="token function">handler</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>view<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <ul> <li>update:</li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> viewId <span class="token operator">=</span> <span class="token function">getViewId</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> view <span class="token operator">=</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FindAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>viewId<span class="token punctuation">}</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> prepare<span class="token punctuation">?.</span><span class="token function">Invoke</span><span class="token punctuation">(</span>dbContext<span class="token punctuation">.</span><span class="token function">Entry</span><span class="token punctuation">(</span>view<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">handler</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">,</span> view<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Where for handlers are projection functions defined above.</p> <p>The DbContext is defined as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ECommerceDbContext</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DbContext</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">ECommerceDbContext</span><span class="token punctuation">(</span><span class="token class-name">DbContextOptions<span class="token punctuation">&lt;</span>ECommerceDbContext<span class="token punctuation">></span></span> options<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>options<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">protected</span> <span class="token keyword">override</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">OnModelCreating</span><span class="token punctuation">(</span><span class="token class-name">ModelBuilder</span> modelBuilder<span class="token punctuation">)</span> <span class="token punctuation">{</span> modelBuilder <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Entity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartShortInfo<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> modelBuilder <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Entity</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">OwnsMany</span><span class="token punctuation">(</span>e <span class="token operator">=></span> e<span class="token punctuation">.</span>ProductItems<span class="token punctuation">,</span> a <span class="token operator">=></span> <span class="token punctuation">{</span> a<span class="token punctuation">.</span><span class="token function">WithOwner</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">HasForeignKey</span><span class="token punctuation">(</span><span class="token string">"ShoppingCardId"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// we cannot use the ProductId here</span> <span class="token comment">// we need the autogenerated id</span> <span class="token comment">// to support the automatic add/update/remove</span> a<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Property</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token string">"Id"</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ValueGeneratedOnAdd</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> a<span class="token punctuation">.</span><span class="token function">HasKey</span><span class="token punctuation">(</span><span class="token string">"Id"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>How to connect all the dots (event bus, event handlers, projections, Entity Framework)? Let’s start by defining what we’d like to achieve.</p> <p>My motivations:</p> <ul> <li>easy to use and self-explanatory API for registering projections. A fluent pattern is usually helpful for that needs,</li> <li>extendible approach, with possible fallbacks,</li> <li>registering everything into regular DI containers to integrate with other application services.</li> </ul> <p>Sample API could look as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Configuration</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token function">AddShoppingCartsModule</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token operator">=></span> services <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddDbContext</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ECommerceDbContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">,</span> ECommerceDbContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> builder <span class="token operator">=></span> builder <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartInitialized<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ShoppingCartDetailsProjection<span class="token punctuation">.</span>Handle<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">UpdateOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> e <span class="token operator">=></span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> ShoppingCartDetailsProjection<span class="token punctuation">.</span>Handle<span class="token punctuation">,</span> <span class="token punctuation">(</span>entry<span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token operator">=></span> entry<span class="token punctuation">.</span><span class="token function">Collection</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>ProductItems<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">LoadAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">UpdateOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductItemRemovedFromShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> e <span class="token operator">=></span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> ShoppingCartDetailsProjection<span class="token punctuation">.</span>Handle<span class="token punctuation">,</span> <span class="token punctuation">(</span>entry<span class="token punctuation">,</span> ct<span class="token punctuation">)</span> <span class="token operator">=></span> entry<span class="token punctuation">.</span><span class="token function">Collection</span><span class="token punctuation">(</span>x <span class="token operator">=></span> x<span class="token punctuation">.</span>ProductItems<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">LoadAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">UpdateOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartConfirmed<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> e <span class="token operator">=></span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> ShoppingCartDetailsProjection<span class="token punctuation">.</span>Handle <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartShortInfo<span class="token punctuation">,</span> ECommerceDbContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> builder <span class="token operator">=></span> builder <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartInitialized<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>ShoppingCartShortInfoProjection<span class="token punctuation">.</span>Handle<span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">UpdateOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductItemAddedToShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> e <span class="token operator">=></span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> ShoppingCartShortInfoProjection<span class="token punctuation">.</span>Handle <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">UpdateOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductItemRemovedFromShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> e <span class="token operator">=></span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> ShoppingCartShortInfoProjection<span class="token punctuation">.</span>Handle <span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token generic-method"><span class="token function">UpdateOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartConfirmed<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> e <span class="token operator">=></span> e<span class="token punctuation">.</span>ShoppingCartId<span class="token punctuation">,</span> ShoppingCartShortInfoProjection<span class="token punctuation">.</span>Handle <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It could be even more brilliant, but the key to the right design is to be careful not to overdo it. I think that it’s self-explanatory, explicit. You can read it as decent documentation.</p> <p>How to build such an API? Let’s start with defining our generic event handlers classes.</p> <p>Add new item projection:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">AddProjection<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span> <span class="token keyword">where</span> <span class="token class-name">TView</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token keyword">where</span> <span class="token class-name">TDbContext</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DbContext</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">TDbContext</span> dbContext<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TView<span class="token punctuation">></span></span> create<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">AddProjection</span><span class="token punctuation">(</span> <span class="token class-name">TDbContext</span> dbContext<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TView<span class="token punctuation">></span></span> create <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>dbContext <span class="token operator">=</span> dbContext<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>create <span class="token operator">=</span> create<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">TEvent</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> view <span class="token operator">=</span> <span class="token function">create</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token function">AddAsync</span><span class="token punctuation">(</span>view<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Update existing item projection:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">UpdateProjection<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IEventHandler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span> <span class="token keyword">where</span> <span class="token class-name">TView</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token keyword">where</span> <span class="token class-name">TDbContext</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DbContext</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">TDbContext</span> dbContext<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span></span> getViewId<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TView<span class="token punctuation">></span></span> update<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>EntityEntry<span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span><span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">></span><span class="token punctuation">?</span></span> prepare<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">UpdateProjection</span><span class="token punctuation">(</span> <span class="token class-name">TDbContext</span> dbContext<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span></span> getViewId<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TView<span class="token punctuation">></span></span> update<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>EntityEntry<span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span><span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">></span><span class="token punctuation">?</span></span> prepare <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>dbContext <span class="token operator">=</span> dbContext<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>getViewId <span class="token operator">=</span> getViewId<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>update <span class="token operator">=</span> update<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>prepare <span class="token operator">=</span> prepare<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">TEvent</span> @<span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> viewId <span class="token operator">=</span> <span class="token function">getViewId</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> view <span class="token operator">=</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FindAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span>viewId<span class="token punctuation">}</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> prepare<span class="token punctuation">?.</span><span class="token function">Invoke</span><span class="token punctuation">(</span>dbContext<span class="token punctuation">.</span><span class="token function">Entry</span><span class="token punctuation">(</span>view<span class="token punctuation">)</span><span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token function">update</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">,</span> view<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> dbContext<span class="token punctuation">.</span><span class="token function">SaveChangesAsync</span><span class="token punctuation">(</span>ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Having them, we can define the main fluent API starting point to orchestrate our registration:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EntityFrameworkProjection</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">IServiceCollection</span> <span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>EntityFrameworkProjectionBuilder<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span><span class="token punctuation">></span></span> setup <span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TView</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token keyword">where</span> <span class="token class-name">TDbContext</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DbContext</span></span> <span class="token punctuation">{</span> <span class="token function">setup</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">EntityFrameworkProjectionBuilder<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span><span class="token punctuation">(</span>services<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> services<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>And the main builder that glues all together:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">EntityFrameworkProjectionBuilder<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span> <span class="token keyword">where</span> <span class="token class-name">TView</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token keyword">class</span></span> <span class="token keyword">where</span> <span class="token class-name">TDbContext</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">DbContext</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">readonly</span> <span class="token class-name">IServiceCollection</span> services<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">EntityFrameworkProjectionBuilder</span><span class="token punctuation">(</span><span class="token class-name">IServiceCollection</span> services<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>services <span class="token operator">=</span> services<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">EntityFrameworkProjectionBuilder<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">AddOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Func<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TView<span class="token punctuation">></span></span> handler<span class="token punctuation">)</span> <span class="token punctuation">{</span> services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span>handler<span class="token punctuation">)</span><span class="token punctuation">;</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEventHandler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span><span class="token punctuation">,</span> AddProjection<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">EntityFrameworkProjectionBuilder<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">UpdateOn</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> <span class="token keyword">object</span><span class="token punctuation">></span></span> getViewId<span class="token punctuation">,</span> <span class="token class-name">Action<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">,</span> TView<span class="token punctuation">></span></span> handler<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>EntityEntry<span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span><span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">></span><span class="token punctuation">?</span></span> prepare <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span>getViewId<span class="token punctuation">)</span><span class="token punctuation">;</span> services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span>handler<span class="token punctuation">)</span><span class="token punctuation">;</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddTransient</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>IEventHandler<span class="token punctuation">&lt;</span>TEvent<span class="token punctuation">></span><span class="token punctuation">,</span> UpdateProjection<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TEvent<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>prepare <span class="token operator">!=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> services<span class="token punctuation">.</span><span class="token function">AddSingleton</span><span class="token punctuation">(</span>prepare<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">EntityFrameworkProjectionBuilder<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">QueryWith</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TQuery<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>IQueryable<span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span><span class="token punctuation">,</span> TQuery<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span><span class="token punctuation">></span></span> handler <span class="token punctuation">)</span> <span class="token punctuation">{</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddEntityFrameworkQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TDbContext<span class="token punctuation">,</span> TQuery<span class="token punctuation">,</span> TView<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>handler<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">EntityFrameworkProjectionBuilder<span class="token punctuation">&lt;</span>TView<span class="token punctuation">,</span> TDbContext<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">QueryWith</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TQuery<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>IQueryable<span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span><span class="token punctuation">,</span> TQuery<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> Task<span class="token punctuation">&lt;</span>IReadOnlyList<span class="token punctuation">&lt;</span>TView<span class="token punctuation">></span><span class="token punctuation">></span><span class="token punctuation">></span></span> handler <span class="token punctuation">)</span> <span class="token punctuation">{</span> services<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AddEntityFrameworkQueryHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TDbContext<span class="token punctuation">,</span> TQuery<span class="token punctuation">,</span> TView<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>handler<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>It may look a bit scary, but if you have a look, then it’s pretty straightforward. It’s just a bunch of the registration to dependency injection containers. We’re registering projection functions that are then injected into projection classes and registered as event handlers.</p> <p>We don’t have to use marker interfaces, as we’re pushing the check to the edges: the registration code. We still are safe and have the compiler check if we didn’t put too many params or inverted their order. If we need more sophisticated or customised logic, we can still fall back and define the custom event handler that will do the magic.</p> <p>Before I finish, please have a look at the <em>ShoppingCartDetailsProjection</em> definition. As you see, I’m not splitting projections per table. I’m breaking it per read model. For the shopping cart details, the read model is built from the cart data and added product items. It’s an implementation detail that they’re split into two separate tables. Thus we should still keep the centralised business logic. For more modelling bits of advice, read <a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">“How to create projections of events for nested object structures?”</a>.</p> <p>As you see, with proper composability, we can create straightforward and maintainable code without head-scratching inheritance problems and generic or reflection magic. I hope this article will help you design better Event-Driven processing, as those patterns can be applied not only to Entity Framework but also to general projections handling.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. you can check the whole solution in the <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/71">Pull Request</a>.</p><![CDATA[10 notes on the 10th blogging anniversary]]>https://event-driven.io/en/thoughts_on_tenth_blogging_anniversary/https://event-driven.io/en/thoughts_on_tenth_blogging_anniversary/<p>Yesterday, precisely ten years passed since I released my first blog post. It’s still available in the original place (in Polish) at <a href="https://oskar-at-net.blogspot.com/2011/09/witam-jest-to-moj-pierwszy-wpis-na.html">BlogSpot</a>. If you know Polish and think about blogging, but you’re afraid to start, check it. It should convince you that you’ll be better than me.</p> <p>I’m not great at dates. I would forget about it if my wife didn’t remind me of this fabulous cake.</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/029df6830108757c3071f76bb079e489/c59e8/10th.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 133.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAXAQADAQAAAAAAAAAAAAAAAAAAAQID/9oADAMBAAIQAxAAAAF5esid4gDZ1zLaUpKj/8QAGhAAAgMBAQAAAAAAAAAAAAAAAQIAAxESE//aAAgBAQABBQKy1cr2tg0s4pZ7DvlGAL2lPTppyDOVAwGf/8QAFhEBAQEAAAAAAAAAAAAAAAAAABIQ/9oACAEDAQE/AclL/8QAFhEBAQEAAAAAAAAAAAAAAAAAABIQ/9oACAECAQE/AcpT/8QAHhAAAgICAgMAAAAAAAAAAAAAAAERIQISMkEDEzH/2gAIAQEABj8CjFqR7Fi18ct/LLUJnNmHsh9GPcI5FopFn//EABwQAQADAAMBAQAAAAAAAAAAAAEAESExUXFBwf/aAAgBAQABPyFFmWS0ZawvmK28l6h7P6BUVWqUzPEAyj9HcBRSux49jb+COOwi0CeECCBan//aAAwDAQACAAMAAAAQ8zS9/8QAGBEAAwEBAAAAAAAAAAAAAAAAAAERITH/2gAIAQMBAT8QXK+lM6i3p//EABcRAAMBAAAAAAAAAAAAAAAAAAABIRH/2gAIAQIBAT8QZhTplQ//xAAdEAEAAwADAQEBAAAAAAAAAAABABEhMUFRcYHw/9oACAEBAAE/EEbmUXgV/EbRwEsqzCXtSwAHHSZyJLJvkWj1inz5z+Q8k7gRL6wWot3fMMLwACdOj7DYYdAJX2KIMseIbrTcLlDkNSf/2Q=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="10th" title="10th" src="/static/029df6830108757c3071f76bb079e489/c60e9/10th.jpg" srcset="/static/029df6830108757c3071f76bb079e489/37402/10th.jpg 200w, /static/029df6830108757c3071f76bb079e489/4cda9/10th.jpg 400w, /static/029df6830108757c3071f76bb079e489/c60e9/10th.jpg 800w, /static/029df6830108757c3071f76bb079e489/6c738/10th.jpg 1200w, /static/029df6830108757c3071f76bb079e489/c59e8/10th.jpg 1536w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>It’s great to have such an anniversary, but it’s even better to have a good wife.</p> <p>Even though it’s been ten years, I still don’t feel like an expert in blogging, but let me share ten notes/suggestions for my ten years of blogging:</p> <ol> <li><strong>Write for yourself but consider the audience.</strong> Do not try to search for topics you’re not familiar with. I initially started my blog to play with new stuff and write my findings of the daily work. I’m sometimes saying that I’m writing not to forget what I learned. It’s nice to go to your blog and find an answer from younger you. People will see when you try to explain something that you don’t know. It’s okay not to be an expert and explain your struggles, but that’s much different from showing it as a best practice. Still, it’s worth considering an audience and finding a middle ground. Nothing’s more motivating than getting feedback. People are not great at giving feedback, so if you go crazy writing about a niche inside the niche, then the chance for the input is even lower.</li> <li><strong>Done is better than perfect.</strong> The hard truth about your first articles is that no one will read them. Maybe a wife, uncle or proud grandma. That might be not motivating, but it’s also a chance to make mistakes. What you write is not put in stone. You can update the post later. Of course, you should not be shitposting and take care of the quality, especially stuff like grammar. Typos and obvious mistakes that can be fixed by spell-check tool are incredibly annoying. Other than that, publish when it’s good enough. You can expand it later, rewrite or, in the worst case, throw it away. To begin, you have to begin.</li> <li><strong>Build a habit.</strong> Define a schedule, e.g. once per week or per month. It’s safer to start rarely and then write more often when you find the rhythm. You won’t risk putting too much pressure on yourself. If you’re stressed and forcing yourself, then the outcome will also be hard for the readers. Still, building a cadence helps to motivate yourself. You can also consider putting a time limit for writing the blog post, e.g. two hours every other Monday. At least, that helps me to manage my perfectionism. I know that whatever will happen, I’m sitting in the evening and have to send it before I go to bed. It’s also essential for the readers (once you get them) because they know when to expect a new post.</li> <li><strong>Run a newsletter.</strong> That helps in building the community around you. Also, if you’re too shy or for some reason you don’t want to share your thoughts, then writing an e-mail in the blogging form might be a good alternative for you. I managed to build a cadence when I started running my <a href="https://www.szkola-event-sourcing.pl/">Polish newsletter</a>. The E-mail also gives closer relation with readers, and for some can be easier to send you the feedback. It’s been over two years since I sent a blog-a-like e-mail with my thoughts. Some of them get later published as my regular blog. There are mailing platforms like <a href="https://www.mailerlite.com/signup">Mailerlite</a>, <a href="https://login.mailchimp.com/signup/?plan=free_monthly_plan_v0">MailChimp</a> or <a href="https://app.convertkit.com/users/signup?plan=free-limited">ConverKit</a> that offer free plans.</li> <li><strong>Store somewhere bits of advice that you’re giving.</strong> For sure, you’re having a lot of internal project discussions, answering questions for your colleagues. It is an excellent source for blog topics. If someone asked you for help and you helped them, others may also benefit from that. I’m not great at making notes, but once I started to put them into the same place (private git repo with markdown) and group them by topics, it helped me build the foundations for the articles.</li> <li><strong>Do not expect much.</strong> I don’t see clear signs of impact on my life made by blogging. I neither got a sponsoring job offer nor earned a penny from it. However, the implicit impact is indisputable. Learning in public helps a lot (also read my <a href="https://event-driven.io/en/how_to_start_with_open_source/">advice about doing Open Source</a>). I learned better to express my thoughts, to put the correct arguments. It helps in work and life in general. I also synthesised what I learned and had some “a-ha moments” during writing. I was finding surprising insights I didn’t consider before investigating the topic. Of course, I know a few people besides my family who read my blog, but I’m far from being an Instagram celebrity. And that’s cool.</li> <li><strong>Use the right tooling.</strong> I switched the tooling a few times. I started with BlogSpot, then switched to WordPress, now <a href="https://github.com/oskardudycz/event-driven.io/">I’m running it on GatsbyJS</a>. Tooling evolved, but I started with the simplest possible option and switched when it was blocking me for some reason. Do not build your own blog engine or try to use all sophisticated tooling. Focus on blogging and selecting tooling that will enable efficiency instead of distraction during your writing. If you want to blog in English, use <a href="grammarly.com">Grammarly</a>. If you’re writing in your native language, find a similar tool that will help to polish your writing (e.g. <a href="https://languagetool.org/pl">LanguageTool</a>). Speaking about Polish, I recommend <a href="https://www.ortograf.pl/">Ortograf.pl</a>, <a href="https://www.jasnopis.pl/">Jasnopis.pl</a>.</li> <li><strong>Read twice before posting.</strong> Please do, at least twice. You’ll be surprised how many apparent mistakes you made. Don’t be lazy leaving that to your readers.</li> <li><strong>Keep your language simple.</strong> Write short sentences. <a href="https://hemingwayapp.com/">Hemingway did that and was fine</a>. You’ll also be okay with that. I’m Oskar, but not Wilde, and you’re also not. Don’t try to be sophisticated just for the sake of being such. Shorter sentences are easier to read and better to keep the pace. If you want to get more theory, I recommend you two books:</li> </ol> <ul> <li><a href="https://www.goodreads.com/book/show/53343.On_Writing_Well">“On Writing Well: The Classic Guide to Writing Nonfiction On Writing Well: The Classic Guide to Writing Nonfiction”</a>,</li> <li><a href="https://www.goodreads.com/book/show/40063024-dreyer-s-english">“Dreyer’s English: An Utterly Correct Guide to Clarity and Style”</a>.</li> </ul> <ol start="10"> <li><strong>Be visible and advocate for your work</strong>. It’s not like the good work will be visible by itself. You have to promote it. For the majority (including me), promoting may sound like something terrible. However, if we believe that what we wrote is good, we should help people find it. Write it on Twitter, Facebook, Linked In, or other platforms you prefer. Of course, do not do <a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ">Rickrolling</a>, do not spam communities, or immediately attack people with your link. Explain first, then you can link your article as an extension. Before posting a link on a user group, make sure that its moderators are okay with that. Also, be a community member, don’t treat it as an advertisement platform. If you help others and, from time to time, post a link, then people will appreciate it. For the inversed proportions? Yes, they won’t.</li> </ol> <p>I hope that this helps you a bit. Take those as thoughts that worked for me. Your experience might be different.</p> <p>The final piece of advice is: just have fun!</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Anti-patterns in event modelling - State Obsession]]>https://event-driven.io/en/state-obsession/https://event-driven.io/en/state-obsession/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a7fb115c5834839b62fb16743970dc03/d2429/2021-09-15-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAAB30lEQVQoz2NgAANGRkYGBgYZGZmurq7p06ZNmNDf09M9ecrEadO6O9pKaquyS4uSSvLjfTztGTABExMTAwODpqbmwUMHb968dfXa9dt37549dfzwvm0b1q7csW3T4YN7zp892dJUDbcJASB8fl6+OW3lz+5duHL62PKpLRf3zK9I8JTg5YgL875+fvOrh8cn9zXg1CwiJlZXWbZr/fztK+a054Yd37Tg2JxJomxs/s7m316e/Pf1+vwZXVg0Q5ytoa21bvX8Qwf27Fw4bVln2YaJLbva6kOVFZpKUv7/f/X/292Fcyfg1KylqfXu9aMf314+v3l8Yn7ExaWzrk7snh0X2lmX8///6/+/ny5ZMBW3Zi2tnz++Pr175eD2FTZakvNy0i+1NNqqqTZUpv///+Lf19uL5uG2WUdb+8O3L7d/fNt/75KVjW5OXPKE3CImBmZrW/Pr13b/eX9+5vQ2nJp1tTUP37nSf+zQjDMnSqb3qYelW2XWKRrbiOmZO6ZlXT2/ZUJ3DbbQZgLx5VUUl+zYMnPDpsWbN09euTqwsm3C/KXT5y+eMHve5HmLVq5aWl9bgkUzA5jLycOjaWmpbGBs4uRgYm8ro6Ejr6Wnpm+soKWroq2vpqGmqCCLrAsAmpTSULAomEsAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/a7fb115c5834839b62fb16743970dc03/a331c/2021-09-15-cover.png" srcset="/static/a7fb115c5834839b62fb16743970dc03/36ca5/2021-09-15-cover.png 200w, /static/a7fb115c5834839b62fb16743970dc03/a3397/2021-09-15-cover.png 400w, /static/a7fb115c5834839b62fb16743970dc03/a331c/2021-09-15-cover.png 800w, /static/a7fb115c5834839b62fb16743970dc03/d2429/2021-09-15-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Some time ago, I tackled the first event modelling anti-pattern: <a href="/en/property-sourcing/">“Property Sourcing”</a>. Today I’d like to tackle the next one: <em>“State Obsession”</em>.</p> <p>I have already written <a href="/en/bank_account_event_sourcing/">that Bank Account this is not the best example</a>, but let’s try again. Let’s assume that we would like to model financial transactions. We may have different transaction types: cash deposit, debit card payment, foreign or crypt currency, even a check. In the end, the most noticeable effect is the change in account balance. Deposits increase it; withdrawals decrease it.</p> <p>We may conclude that we could do a <em>BalanceUpdated</em> event that would store information on how much the balance changed. Positive value for deposits, negative for withdrawals, e.g.:</p> <p><strong>BalanceUpdated</strong> {<em>Amount</em>: 50}</p> <p><strong>BalanceUpdated</strong> {<em>Amount</em>: -50}</p> <p><strong>BalanceUpdated</strong> {<em>Amount</em>: 100}</p> <p><strong>BalanceUpdated</strong> {<em>Amount</em>: -20}</p> <p>We could use those events to calculate the total balance (<em>80 = 50 - 50 + 100 - 20</em>).</p> <p>Such event structure can look like a reasonable idea. We made our processing <a href="/en/generic_does_not_mean_simple/">generic and simple, right?</a> However, by trying to standardise processing, we flattened business information. Ignoring the specifics of those operations and not creating detailed events like <em>DepositRecorded</em>, <em>CashWitdhrawnFromATM</em>, <em>TransactionVoided</em>, we’re risking the loss of important business information. Events are business facts and should be focused on the outcome of the operation. Ultimately, withdrawing cash from an ATM will have different properties than paying by a credit card. When modelling events, it’s important not to think about the state change but about what exactly has happened. We should keep things simple but not oversimplified.</p> <p>Therefore, in my opinion, it is better to replace the generic event with more specific ones, even if they seem very similar at the design time. It is much more valuable to have this sequence instead of the previous one:</p> <p><strong>DepositRecorded</strong> {<em>Amount</em>: 50}</p> <p><strong>CashWithdrawnFromATM</strong> {<em>Amount</em>: -50}</p> <p><strong>IncomingTransferRecorded</strong> {<em>Amount</em>: 100}</p> <p><strong>CreditCardPaymentMade</strong> {<em>Amount</em>: -20}</p> <p>Just by looking at them already, we can see what was happening.</p> <p>An even worse problem would be if we modeled the <em>BalanceUpdated</em> event as storing the current account balance after the transaction was registered, for example:</p> <p><strong>BalanceUpdated</strong> {<em>Balance</em>: 50}</p> <p><strong>BalanceUpdated</strong> {<em>Balance</em>: 0}</p> <p><strong>BalanceUpdated</strong> {<em>Balance</em>: 100}</p> <p><strong>BalanceUpdated</strong> {<em>Balance</em>: 80}</p> <p>There is no information about the type of transaction. We also lost the only specific information: transaction amount. We need to know the current and previous balance to be able to calculate the transaction amount.</p> <p>In the case of these events, the specific information is that you deposited 50$ at the bank and then withdrawn them at the ATM. In the case of the transaction processing, the account balance is derived information. It’s not specific for the transaction that happened but calculated based on them. We should not overlook critical details. It is the direct way to losing data. If the algorithm or taxes change, how will you know what the transaction amount was?</p> <p>Of course, life is not only black and white. Sometimes we need to make pragmatic decisions. For instance, we must remember that the most common use case for events is (re)building read models. Projections that are used for that should be relatively simple code, without much business logic. In our sample case, we would not like to duplicate the logic of calculating the account balance in many different places. Especially since they don’t have to be so trivial as subtract here, sum there. The calculation might get complex with taxes, bank commissions, currency conversions, etc. We should try to keep such business logic on the write model. Sometimes it’s worth adding a little redundancy to our events. In our sample case, it could be adding an information about the current balance. They could look like this:</p> <p><strong>DepositRecorded</strong> {<em>Amount</em>: 50, <em>Balance</em>: 50}</p> <p><strong>CashWithdrawnFromATM</strong> {<em>Amount</em>: -50, <em>Balance</em>: 0}</p> <p><strong>IncomingTransferRecorded</strong> {<em>Amount</em>: 100, <em>Balance</em>: 100}</p> <p><strong>CreditCardPaymentMade</strong> {<em>Amount</em>: -20, <em>Balance</em>: 80}</p> <p>It gives us several advantages:</p> <ul> <li>we maintain the business logic in the write model. Read models do not have to be aware of it.</li> <li>we have frozen information about the balance, even if the balance calculations (e.g. taxes) change. Thanks to that, we can avoid the need to version the calculation formulas.</li> <li>generating the reading model is more manageable, and more importantly, easier to deal with idempotency. If we have an ordering guarantee, applying the event multiple times will have the same effect. If we only have the transaction amount, we must have a dedicated mechanism to avoid charging the same transaction multiple times.</li> </ul> <p>This not only apply to read models, but equally about any downstream consumers. Events can trigger other workflows as well (e.g. fraud detection, etc.). They don’t have to be only to update the reading models. Events are facts. How they are interpreted depends on the subscribers.</p> <p>It is a bit grey area when to allow redundancy and where not. You have to analyse the pros and cons each time. We should avoid putting a derivative state in our events (e.g. balance) when possible. We should try to <a href="/en/events_should_be_as_small_as_possible/">keep them as concise and small as possible, but not smaller</a>.</p> <p>We can pragmatically add additional information or make transformations to help subscribers, but that should not be the basis of our considerations. It should be an optimisation that we have carefully crafted.</p> <p>In an event-based approach, we don’t have to think about everything at once. First, we should focus on how to preserve a business fact best, then how to use it. We can create new read models based on already existing events. As long as we register all the essential information, we will be able to use it later. If we follow these rules, we don’t even have to design read models while working on the business logic. Primarily, by combining the read model with the event model, we lose the autonomy of these models.</p> <p>One of the most significant benefits of using Event Sourcing is not losing data. We should focus on recording all information related to the business operations results. Read models or other modules should take it from there.</p> <p><strong>To get it right, you have to change your mindset of thinking “what is” to “what happened”.</strong></p> <p><strong>Read also other article in Anti-patterns in event modelling series:</strong></p> <ul> <li><a href="/en/property-sourcing/">Property Sourcing</a>,</li> <li><a href="/en/i_will_just_add_one_more_field/">I’ll just add one more field</a>.</li> <li><a href="/en/clickbait_event/">Clickbait event</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[How to slice the codebase effectively?]]>https://event-driven.io/en/how_to_slice_the_codebase_effectively/https://event-driven.io/en/how_to_slice_the_codebase_effectively/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/9d2c24354312f0bdc8a5b61c91ccb030/d2429/2021-09-08-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AB81LDMxJ3o/I5NMJa6RfKKjo62orNvKi8LENcvStp6bmGpkZ15WR3VpQ4+DVammZrWsd4h2On1+U4KNfwAhNSwrLypNMSVxOiOtnJOkn5yKg3+ipIOQmWaXnZNbYFw9P0UpKChCOTdtY0+5sz60mVC3lTKrxkWisXAAIzguJSonOjo4Tz00Vk5MY1xiX0NJSkhOY11ieHRtZF5WaWJgnKCa4N3P9eTE/vfI/vzM/+2S6fqZ4euUACAqJi8vK0I9NjYxLVFLSX1lZ4xaY0dFRTQuKT02MzkxL6iUZ+C1jcuKcNSzhtvBo9/Eod/Oo+TJo+DRqACDXUKjdVmValVQQDZGQD9nZWpnb3dKS08nJyUvKikuLCubhFi4cECkPByoh1WsjWOtlnGxo3+7qn3LuZUAn25M1JhyoW5YPSolOzg5YVxgZGVsXmRtVltlUlRcX2hzmolwzZxj0HYntYRdspl3rZt+s62HxLiSz76gAI5aPaVvUe29lpNwVC8qJ1FPVF5gZmJocWNqc2Zud2p1gp2NdcSfW62HIZB2V7OfdbSigbmxkraqj8q4nQCXVTdnMSKFSzPlqXihgWhLSUxhY2phZGtcYGhobXVka3WSf2ewj15haSxuYkawlW6pmnG5pYa5rZHFuZwArm1DllQzSSUismxC0YdllHVlSEhNWltgVFZbU1ZcXWNrkoZurZdrVYVHZ2VCqo5vq5R9t6WEyLWS08GgAMuJTrxwQK5sRrl/XZhYQ7uPfWpub2RsdHJ7g2xzfFdWW15eV1tZRlNbO3ZmRJB7XY98U6CQbqmZfaSadgDDh1LFh1vIhlSudk2ZZ06eeGliWFhGSk1AQEA9PUA9PEA7OTo9Oj09Njg3MzE9NzJAODNDPTpCPDlLRkxYqioAnYzLqAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/9d2c24354312f0bdc8a5b61c91ccb030/a331c/2021-09-08-cover.png" srcset="/static/9d2c24354312f0bdc8a5b61c91ccb030/36ca5/2021-09-08-cover.png 200w, /static/9d2c24354312f0bdc8a5b61c91ccb030/a3397/2021-09-08-cover.png 400w, /static/9d2c24354312f0bdc8a5b61c91ccb030/a331c/2021-09-08-cover.png 800w, /static/9d2c24354312f0bdc8a5b61c91ccb030/d2429/2021-09-08-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>The structure of projects is as sensitive as the discussions of spaces versus tabs or indentation size.</strong> When we enter someone’s apartment, even when it is decorated based on default sets from IKEA, it always looks a bit different. And it is absolutely justified. Different apartment sizes and room layouts make a difference.</p> <p>It is similar to our software projects. Every has slightly different assumptions and features, but some common characteristics can be distinguished, right?</p> <p>Like everyone else, I went from a strictly technical split, where I had a folder for services, contracts, mappers, data models, etc. At that time, I thought that then I had everything prim and proper. However, that assumption has changed.</p> <p><a href="https://github.com/oskardudycz/EventSourcing.NetCore/">Some time ago, I changed my Event Sourcing in .NET samples</a>. Now they are sliced entirely by business functionality, not technical breakdown. Why did I do that?</p> <p>My path leading to that decision was evolutionary. When I was creating the repository, I was at the stage when I was dividing the system into modules. Each module was a separate project. The first division in the project were aggregates, so business submodules. However, within the folder, the division was still technical. Different subfolders for commands, events, value objects etc.</p> <p>It was an acceptable solution, but I still felt uneasy. The breakthrough came when…</p> <p>…I worked longer with Angular. In the “new Angular”, it’s essential to break your codebase into components. In the same folder, we keep both the HTML view and the component TypeScript code. What’s more, we also hold the unit tests together! At first, I found it strange, but then I realise that it helps in being effective.</p> <p><strong>Usually, when we work on a given functionality, we use the same template:</strong></p> <ul> <li>API endpoint,</li> <li>request type,</li> <li>some class to handle it,</li> <li>data model.</li> </ul> <p><strong>Classically, we spread it between different places. We have to jump between several folders, files, etc.</strong> Of course, IDEs shortcuts help us with this, but it does not change that we have to bounce continuously from one place to another. We switch the context invariably. I don’t need to tell you how effective work is when we are disturbed by notifications on the phone or social media. Jumping between files can be compared to this. When we have everything nearby, and preferably in one file, we can be more efficient. We may not need even three monitors or one as wide as rugby player shoulders.</p> <p>Keeping such a split works great with CQRS. It segregates our operations and slices the application code vertically instead of horizontally. Event Sourcing introduces an even more significant improvement because we do not need a unified data model (e.g. DBContext in EntityFramework). Each operation ends with an event that we can keep in the command folder.</p> <p>It looks like this:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 303px; height: auto" > <a class="gatsby-resp-image-link" href="/static/bd53ad51c9ab48e3db35fa7ea977d4ee/8cd94/cqrs_structure.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 199.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAEJElEQVRIx5VWy24bRxDk/5jivpf7nPdjl6QkSwFyCRDYV/sWwIYTINf8dgXdpCRSFmnq0FgJmilVd1fX9KJaN9jKHjsv0A4SUkooqTAOA9brBn0/cgghUVUVsixDnudnY5HlOfygMWuJbdDQ2sIaAykVuq6HlAZSKAzDeCVgliGrCphmxIMzmLZ3zK5tewanaJsWaZo8g10CXeR5wQfKssagJPzDFloZGK1xc3ODm9UKqyRBkqTIMrpUXIxFli6Rp0tkyQdUZYJ1m0NpC+8sjBZwRsLIAUaNKPMVn6M752JR6D/xFLX7jC58wsP9I+LjV0y//wV99xVy9wXy9gsq+wnH59+KRbn5gaeod/+gv/2GGHbY/fEv7j//h2L+jix+Qz59R7H5gWr7N0qKo3vHsW8KFZm/KfKiQLkesJUptl5hnneY4oQpRoQQIYXkcy93TmNx/Euev3zTJEWnW0ivsJm3iHGCtQ5SCLRddxZ08brt2eGbJhnWag0zW0QfYYyD9wHee2htzmryBPD4QJll6EzDDJ1xzM45x0yddRiG4TrAoijQNC3uTIHJjLDWMzMCoS9N0UuJ8pOfzzKkGZ5EhjloOBfgnYdznptCqSZJgjRND/euBNypHLNXCGFC8AHTNDOgoKa0LZqmuT7lJ4aTkwhxOqRqEeMelBjTaL4DcI2dzrHxGtO0YRBqyDxvuJaU7mq1ulzD106yuknQ6BYqaAQfGYxSV0qjruuj8xcYvgBmzzoUQcJTp8O+jsZYdF171sLebAqlXRcZetNCR8s1s5TyNLMex3G8HnCvw4ZruPUaIczMjmpHqU/TxPU8Z7QXdJhjcoq7/NRZem94isryPYB72Wwk6VAjTjO7DTXEWou6ro5E/TPoL4St4Q/saGJoWvq+fwG5PuU1wpgjWglLI+cj2qbB8sMHfmeI4antXdGUe5XhNhoY66CVYpeJh6khT3xXU6jojaigzB5ISQkxCvT9wMFz/KuUTyclQ+s7jEowIzIEepcp1WOnyS415fgPRZ6jdR0EA0ZmGUKA1hplUVx88Bev2aVphiTN0bgGoxr31jVv2KHPSeUsw7KqUFcV4ljAegWlNS9OBHZsCJdATwCLssS6LjHbGlYblotS6rRub0jlp2VpX+CDMZQV2mnAKAULmuo3nnmQzjLkdIsSQkmEjztoqfm5pBmmUSMzvWbzOgEsipJlEh62vB0YY3jUSDLjKFjw17B8xVDA3c+8zpFMKF0yVGJLYn8XYFHkGNUA9zDz9moPDKUUWC6X16dMB6q6husHPHoBTUDWo143EEJBSc3/4K135yKgaQf8djAD5wPvL0M/7HdsqXmGyyvq+AxouwGPQUJby3Xbe1+HcZQMSPFa3BcBfS/x6A2mzRYxRH7USdT9IHjzoh2nbfdr3CXA/wGN6DAFBEs0vwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="CQRS structure" title="CQRS structure" src="/static/bd53ad51c9ab48e3db35fa7ea977d4ee/8cd94/cqrs_structure.png" srcset="/static/bd53ad51c9ab48e3db35fa7ea977d4ee/36ca5/cqrs_structure.png 200w, /static/bd53ad51c9ab48e3db35fa7ea977d4ee/8cd94/cqrs_structure.png 303w" sizes="(max-width: 303px) 100vw, 303px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span>.</p> <p><strong>Command folders contain:</strong></p> <ul> <li>file with command and handler, e.g. <em>Carts/AddingProduct/AddProduct.cs</em></li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">AddProduct</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> ProductItem <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">AddProduct</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> cartId<span class="token punctuation">,</span> <span class="token class-name">ProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cartId <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>cartId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">AddProduct</span><span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">HandleAddProduct</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ICommandHandler<span class="token punctuation">&lt;</span>AddProduct<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IMartenRepository<span class="token punctuation">&lt;</span>ShoppingCart<span class="token punctuation">></span></span> cartRepository<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IProductPriceCalculator</span> productPriceCalculator<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">HandleAddProduct</span><span class="token punctuation">(</span> <span class="token class-name">IMartenRepository<span class="token punctuation">&lt;</span>ShoppingCart<span class="token punctuation">></span></span> cartRepository<span class="token punctuation">,</span> <span class="token class-name">IProductPriceCalculator</span> productPriceCalculator <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cartRepository <span class="token operator">=</span> cartRepository<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>productPriceCalculator <span class="token operator">=</span> productPriceCalculator<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Task</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">AddProduct</span> command<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span> <span class="token operator">=</span> command<span class="token punctuation">;</span> <span class="token keyword">return</span> cartRepository<span class="token punctuation">.</span><span class="token function">GetAndUpdate</span><span class="token punctuation">(</span> cartId<span class="token punctuation">,</span> cart <span class="token operator">=></span> cart<span class="token punctuation">.</span><span class="token function">AddProduct</span><span class="token punctuation">(</span>productPriceCalculator<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token named-parameter punctuation">ct</span><span class="token punctuation">:</span> ct <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <ul> <li>file with an event command is creation, e.g. <em>Carts/AddingProduct/ProductAdded.cs</em></li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">ProductAdded</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> ProductItem <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ProductAdded</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> cartId<span class="token punctuation">,</span> <span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cartId <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>cartId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ProductAdded</span><span class="token punctuation">(</span>cartId<span class="token punctuation">,</span> productItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Query folders contain:</strong></p> <ul> <li>query with handler, e.g. <em>Carts/GettingCartById/GetCartById.cs</em></li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">GetCartById</span><span class="token punctuation">(</span> <span class="token class-name">Guid</span> CartId <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">GetCartById</span> <span class="token function">Create</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> cartId<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>cartId <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token operator">||</span> cartId <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>cartId<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">GetCartById</span><span class="token punctuation">(</span>cartId<span class="token punctuation">.</span>Value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">HandleGetCartById</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">IQueryHandler<span class="token punctuation">&lt;</span>GetCartById<span class="token punctuation">,</span> ShoppingCartDetails<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">IQuerySession</span> querySession<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">HandleGetCartById</span><span class="token punctuation">(</span><span class="token class-name">IQuerySession</span> querySession<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">this</span><span class="token punctuation">.</span>querySession <span class="token operator">=</span> querySession<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">GetCartById</span> request<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellationToken<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> cart <span class="token operator">=</span> <span class="token keyword">await</span> querySession<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">LoadAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>CartId<span class="token punctuation">,</span> cancellationToken<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> cart <span class="token operator">??</span> <span class="token keyword">throw</span> AggregateNotFoundException<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">For</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCart<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>request<span class="token punctuation">.</span>CartId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <ul> <li>read model with projection, e.g. <em>Carts/GettingCartById/CartDetails.cs</em></li> </ul> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ShoppingCartDetails</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ClientId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">ShoppingCartStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">IList<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span> ProductItems <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> <span class="token keyword">default</span><span class="token operator">!</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">decimal</span></span> TotalPrice <span class="token operator">=></span> ProductItems<span class="token punctuation">.</span><span class="token function">Sum</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span>TotalPrice<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> Version <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartOpened</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">;</span> ClientId <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ClientId<span class="token punctuation">;</span> ProductItems <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">List<span class="token punctuation">&lt;</span>PricedProductItem<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Pending<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductAdded</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> newProductItem <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem <span class="token keyword">is</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">MergeWith</span><span class="token punctuation">(</span>newProductItem<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ProductRemoved</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> productItemToBeRemoved <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> existingProductItem <span class="token operator">=</span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">.</span>ProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span>existingProductItem <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>existingProductItem<span class="token punctuation">.</span><span class="token function">HasTheSameQuantity</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductItems<span class="token punctuation">.</span><span class="token function">Remove</span><span class="token punctuation">(</span>existingProductItem<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> ProductItems<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span> existingProductItem<span class="token punctuation">,</span> existingProductItem<span class="token punctuation">.</span><span class="token function">Subtract</span><span class="token punctuation">(</span>productItemToBeRemoved<span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartConfirmed</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Confirmed<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">ShoppingCartCanceled</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Status <span class="token operator">=</span> ShoppingCartStatus<span class="token punctuation">.</span>Canceled<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">PricedProductItem<span class="token punctuation">?</span></span> <span class="token function">FindProductItemMatchingWith</span><span class="token punctuation">(</span><span class="token class-name">PricedProductItem</span> productItem<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> ProductItems <span class="token punctuation">.</span><span class="token function">SingleOrDefault</span><span class="token punctuation">(</span>pi <span class="token operator">=></span> pi<span class="token punctuation">.</span><span class="token function">MatchesProductAndPrice</span><span class="token punctuation">(</span>productItem<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CartDetailsProjection</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">SingleStreamProjection<span class="token punctuation">&lt;</span>ShoppingCartDetails<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">CartDetailsProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token generic-method"><span class="token function">ProjectEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartOpened<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>item<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">ProjectEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductAdded<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>item<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">ProjectEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ProductRemoved<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>item<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">ProjectEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartConfirmed<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>item<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token generic-method"><span class="token function">ProjectEvent</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ShoppingCartCanceled<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>item<span class="token punctuation">,</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token operator">=></span> item<span class="token punctuation">.</span><span class="token function">Apply</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p><strong>Of course, a project that does not have CQRS or Event Sourcing can also benefit from this.</strong> Rule of thumb: keep things together that change together. Besides reducing the context switching, such a split also improves understanding of what is happening in business, managing dependencies and ultimately even scaling out. It’s easier to extract features into dedicated microservices.</p> <p>What do you think? How does it look in your project?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If you liked the article, read more in:</p> <ul> <li><a href="/en/how_to_cut_microservices/">How (not) to cut microservices</a></li> <li><a href="/en/cqrs_facts_and_myths_explained/">CQRS facts and myths explained</a></li> <li><a href="/en/generic_does_not_mean_simple/">Generic does not mean Simple</a></li> </ul><![CDATA[Will it scale... down?]]>https://event-driven.io/en/will_it_scale_down/https://event-driven.io/en/will_it_scale_down/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4f3b09de025c7a924acb40522d48d033/d2429/2021-09-01-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAAB4ElEQVQoz2P4OHEuMvowcc7nKfOfdUwKUpNJN1UK15X1U5e919T9dcrCDxPmoClmQOO/6539f+bypXHp9vxMr2eW3J9Wps7OMDEw7P/sFe/6Z+PT/KF/7vtZs/4vXtjo7uEtL7QhzfdwRZyZOH+YrsHf6Yvf47f506R5F1sL7rcXZNsY51tpv5qQ/2lhQ7W3taW41PfJ89F0omme937CrLNN2c8nVEVoqCuyMKwJsD6ZG+ohx28sIv554pyPk+Z9wKr5w8S5nyfMfTZ5zqWje97t21BgZc3AwJCrKDzVTkOajUGPX+B1y4TPk+Z/mDgHu+av/XNuzJ5/8NDe/Vs3aEtLCDAzlrtb9ATbSHCyybKxXO3s/TJjyQfUMEOx+cySJceP7J43oZudgUFPlH/d3Cm71i51MNTmYGDoKcm4e/Dge6yaQfonzTu+af3ZU4cnN1YJMjBYivIe2bTi3IFtbqZ6bAwMXuryezeuejpn6af+2R/QNH+eMPfl1HmHt2+4fPF0U2aiPAODi7TgvnWLd29clexhJ8HAYMTHtnhCy5PVGz73zvwwCUnzh4lzv/TPuT1v8Z6ta04e2lMaG6LKwBCkKbNt+dyD2zcu76nLcrNWYGDoLMu+t2Hzx+4ZH2GaAcyHeVqE5KCUAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4f3b09de025c7a924acb40522d48d033/a331c/2021-09-01-cover.png" srcset="/static/4f3b09de025c7a924acb40522d48d033/36ca5/2021-09-01-cover.png 200w, /static/4f3b09de025c7a924acb40522d48d033/a3397/2021-09-01-cover.png 400w, /static/4f3b09de025c7a924acb40522d48d033/a331c/2021-09-01-cover.png 800w, /static/4f3b09de025c7a924acb40522d48d033/d2429/2021-09-01-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="/en/how_to_scale_projections_in_the_event_driven_systems/">Some time ago, I wrote</a> that there aren’t many more annoying questions than <em>“…but will this scale?!”</em>. We usually think prematurely about upscaling. Recently, I had the thought that maybe we should be thinking more often about scaling down?</p> <p>Intel did not think about that and is now losing its position as the market leader in processors (<a href="https://stratechery.com/2021/intel-problems/">read more</a>). ARM processors associated with the Raspberry Pi and other IoT toys have entered the showrooms. The dynamic development of smartphones has helped develop powerful and energy-saving processors that are suitable for heavy use.</p> <p>The Apple M1 processor is one thing, but it’s just the beginning of the revolution. AWS has already introduced its cloud services based on <a href="https://aws.amazon.com/ec2/graviton/">ARM Graviton processors</a>. Other corporations are lagging behind, but of course, they have also started working on their ARM chips:</p> <ul> <li>Google: <a href="https://www.crn.com/news/components-peripherals/google-cloud-hires-intel-vet-uri-frank-to-design-server-chips">https://www.crn.com/news/components-peripherals/google-cloud-hires-intel-vet-uri-frank-to-design-server-chips</a>,</li> <li>Microsoft: <a href="https://www.datacenterdynamics.com/en/news/microsoft-reportedly-developing-its-own-arm-server-chips/">https://www.datacenterdynamics.com/en/news/microsoft-reportedly-developing-its-own-arm-server-chips/</a>,</li> <li>Oracle: <a href="https://www.reuters.com/technology/oracle-launches-arm-based-cloud-computing-service-using-ampere-chips-2021-05-25/">https://www.reuters.com/technology/oracle-launches-arm-based-cloud-computing-service-using-ampere-chips-2021-05-25/</a>.</li> </ul> <p>New opportunities and the immediate appearance of <a href="https://en.wikipedia.org/wiki/ARM_architecture">ARM</a> processors in the <em>“premier league”</em> causes that many companies cannot keep up with adjusting their solutions to support it. ARM chips have their own specificity; for instance, they are not necessarily well suited to traditional database approaches.</p> <p>I have been asked multiple times if Event Sourcing suits the IoT. Potentially, this seems like a great match. Indeed, the IoT model seems compatible with Event Sourcing as we listen to events and then interact. However, this is not as trivial as it may seem. The main problem lies in the data coming from machines and the frequency of the events. Machine measurements might have a much higher frequency than the typical event store can ingress.</p> <p>What’s more, in Event Sourcing, events accumulate business value. IoT data is usually raw. They contain the unprocessed purely technical information. It might be needed to transform them. A typical pattern is to save the measurements using lightweight storage (e.g. simple key/value databases). Then grouping and processing into events that are understandable for business. This event transformation is essential for performance, but most importantly, for raw events to have business relevance.</p> <p>Another point is that most event stores are not lightweight. They were created with larger enterprise projects in mind. Therefore, they cannot always run on IoT devices.</p> <p>For example, in the EventStoreDB, the biggest problem for official ARM support is using the <a href="https://v8.dev">V8 engine</a> for projection and gRPC, which also has issues with ARM servers. That should be solved in another version, but it still takes time to adopt, and multiple dimensions must be taken into account. Plus, a set of tedious regression testing.</p> <blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr">Following the introduction of interpreted projections in <a href="https://twitter.com/hashtag/EventStoreDB?src=hash&amp;ref_src=twsrc%5Etfw">#EventStoreDB</a>, been working of getting the database to run on <a href="https://twitter.com/hashtag/ARM64?src=hash&amp;ref_src=twsrc%5Etfw">#ARM64</a>.<br><br>Here&#39;s a 3 node cluster running on an Android phone! <a href="https://t.co/BBJ6cQsj6O">pic.twitter.com/BBJ6cQsj6O</a></p>&mdash; Arwin Neil Baichoo (@arwinneil) <a href="https://twitter.com/arwinneil/status/1433326264815816705?ref_src=twsrc%5Etfw">September 2, 2021</a></blockquote> <p>The progress is made, but it takes effort and mind shift to adapt.</p> <p>Other types of databases will collide with similar issues. More and more people will expect software developers to run them on lighter and smaller environments. It is not only about IoT, but also serverless etc.</p> <p>This is already directly influencing software development models. Not everyone likes it. For example, .NET is slimmed down with each release. Fewer and fewer functions are run automatically. A startup is getting shorter and more straightforward. Many people get upset that it is done for <em>“Bootcamp people who learned JavaScript”</em> and that it doesn’t matter in <em>“big projects”</em>. Maybe that’s true for traditional monoliths, but not for the emerging use cases. For cases like serverless, initial (<em>“cold”</em>) startup time is critical. The slower it is, the more it costs (read more in <a href="/en/how_money_in_cloud_impacts_architectural_decisions/">“How money in Cloud impacts Architectural decisions?</a>). Also, the simpler it is, the less boilerplate it has, the smaller <a href="/en/sociological_aspects_of_microservices/">cognitive load</a> it has. That impacts adoption and many other cases.</p> <p>The other area is edge computing. Single-page applications, static page rendering and solutions like <a href="https://workers.cloudflare.com/">CloudFlare workers</a> enabled other use cases. Computing is not only distributed to the servers but also to client applications. <a href="https://phiresky.github.io/blog/2021/hosting-sqlite-databases-on-github-pages/">You can even host database on GitHub pages</a>.</p> <p>We’re putting more effort and computation to the edge: web pages, IoT devices, and serverless functions. Thanks to that, we’re reducing the traffic to the servers. By that, we’re getting more scalable architecture. Right now, not only server nodes are used for computations but also client applications. By scaling down pieces of our architecture, we’re giving more capabilities to scale up the whole solution. Still, besides the pros, it also has cons; it brings more complexity and forces us to optimise computing and trim resource usage.</p> <p>As you see, being able to scale down enables a lot of scenarios. It’s so tempting and <a href="https://www.troyhunt.com/serverless-to-the-max-doing-big-things-for-small-dollars-with-cloudflare-workers-and-azure-functions/">cost effective</a>, that this will only grow. I anticipate that the ability to slim down your software and code will become more and more critical.</p> <p><strong>When we hear “will it scale?” the next time, it might appear that it will mean downscaling, not upscaling.</strong></p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Computer says no! Why we might have an issue with Artificial Intelligence soon]]>https://event-driven.io/en/computer_says_no_we_may_have_an_issue_with_ai_soon/https://event-driven.io/en/computer_says_no_we_may_have_an_issue_with_ai_soon/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/044098b6bee5ff036d849ce030d60a1f/d2429/2021-08-25-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACWUlEQVQozxWNW0uTYQCAP/V7z8fvsMO3dkynZoq6uYOnjcY0p9uaU1yyciAaKShqJQmBgiF00R8IKgTtoq6q2/oD0W3/J/bcPRcPjwFMggDBkABsxUqN8t7x5EqTK1sBqAgTmFPCCeGMcE4EIQxjghBGiECIDWhiDLse6J8Y3Do7//uv9u1XYfskIrWDqUW5pJxTjqkCTGEmGeEYU4wpQsTAAEETUSLiiehYcri6efKs2bw6PqoXS05Pb4ALl3HEZYyJc4iHmDC55IRRJhjj3Rj0mI7ryxSKq/n5T1OlL/nJ3wet09Ks12tGuZBcRLjoEHoN0S5hYaGhUFpoIbSBAAR9wKdlIT1eG0t/b7Zvs+U/Lw9fjQzGAPIYTxD6AYCvJmxQdgvQZ4CihGOutLQMBAA0AUdo9N5gMRlfS2fa2embN8/rAxEP4gGp3gn5g7IbTF5zcU3ZTy7fa9tTNmXCwABiiDjGufHhuaG7nXLm4+XOxeZ8xARBQnJDI1urG/Xy0oP8bKVSX1morD3aKE7mov5QNyYAkt4+ZvYlB/pTo2PT+emDVnl3MRXELKZ1wnXjkXDUC4YjXfxeyAsnhHa40FJahmZU2E5yYTm73r6fmqrOjDfKcyNBx4UgREjc0mEtwn475HdCfjvg2kpIyaXgSirbyDxcylbrM52d0saTyvZep1Vt18oWwc2VxcbCg5CUQUd7ftsLuHcCrud3bEsLoThX3XNh/0X68dNcYy21XJ+ora+2WldvL/aPDi8vzvZOT9ebdVsJLxoNeEHH1j5b+yxlW5ZSlmXZ/wHn2mUcSDT4PwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/044098b6bee5ff036d849ce030d60a1f/a331c/2021-08-25-cover.png" srcset="/static/044098b6bee5ff036d849ce030d60a1f/36ca5/2021-08-25-cover.png 200w, /static/044098b6bee5ff036d849ce030d60a1f/a3397/2021-08-25-cover.png 400w, /static/044098b6bee5ff036d849ce030d60a1f/a331c/2021-08-25-cover.png 800w, /static/044098b6bee5ff036d849ce030d60a1f/d2429/2021-08-25-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Some time ago, I was doing a pilot project for two well-known American fast-food brands.</strong> It was about using image processing and artificial intelligence for business analysis. The simplest case was to analyse images from two cameras. The first one would be set up at the entrance to the restaurant, the second at the checkout. By detecting the face of the paying person at the checkout, you could compare it with the moment of entry and calculate the waiting time for the order. Of course, there are potentially unlimited cases when that could be useful. For instance, displaying personalised ads based on the order history from a loyalty card. Nevertheless, let’s skip this area for now.</p> <p><strong>For our algorithm, we used image processing SDK utilised at many USA airports.</strong> The algorithm worked very well, but only for a specific group of people: white people. The algorithm made many mistakes for others with different skin colours and was often wrong in apparent situations. It was bad enough that we decided to not use it in the end. Still, US airports do not have issues with using it… It is not surprising that white people are less often “flashed” during the strip searches than others. There is a much greater chance of making a mistake with people of colour.</p> <p><strong>On 7 November 2019, David Heinemeier Hansson (author of Ruby on Rails) <a href="https://twitter.com/dhh/status/1192540900393705474">unleashed another storm on the Internet</a>.</strong> Apple introduced its credit card. DHH, along with his wife, applied to get it. Despite having joint tax returns and similar age, it turned out that DHH got twenty times the credit limit than his wife. Apple reply can be shortened to <em>“It’s not our fault, it’s algorith decision.”</em></p> <p><strong><em>Computer says no!</em></strong></p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/0n_Ty_72Qds?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p><strong><a href="https://www.technologyreview.com/2021/07/30/1030329/machine-learning-ai-failed-covid-hospital-diagnosis-pandemic/">A recent MIT report</a> shows once again how far the journey is ahead of Artificial Intelligence.</strong> They researched the results of several hundred projects aimed to help in the fight against COVID. Guess how many projects had a positive impact on that battle? ZERO. There were many reasons for that:</p> <ul> <li>no cooperation between teams; each team was reinventing the wheel,</li> <li>using the same set of data for validation tests as was used to train the algorithm,</li> <li>lack of cooperation between scientists and programmers. The programmers didn’t have enough skills to deeply analyse the results to improve the algorithm. Scientists did not have enough programming knowledge to improve the algorithm based on the results.</li> </ul> <p><strong>The results were comical.</strong> The algorithm increased the chances of having COVID when someone was lying down. People with COVID were usually bedridden and had their lungs RTG took while lying down. Researchers used photos of children’s lungs as an example of people who did not have COVID. Instead of learning to detect disease, the algorithm learned to detect children.</p> <p><strong>My master’s thesis is a broad topic.</strong> I used a simple genetic algorithm called <a href="https://en.wikipedia.org/wiki/Simulated_annealing">“Simulated Annealing”</a>. The name comes from hardening steel, which is heated and cooled repeatedly to increase the size of its crystals and reduce their defects. In computer science, it’s used to approximate the global optimum of the given function. I did not harden steel, but I used it to find if the image I generated was similar to the model photo. My goal was to create a program that would based on the 3d model and photos of the room, detect where and what lights are placed, surface parameters, etc. You could then rearrange them and see what the room looks like during the day when the photo was taken in the evening.</p> <p>I conducted the tests for the medium difficulty of the model. Once the algorithm works for it, I thought that covering simple cases will be straightforward, and it will be easier to expand for the more complex ones. After the struggles, I managed to make it work. I showed it to the thesis supervisor. He said, “That looks promising, but could you check and describe how it works for simple cases like a cylinder, a tetrahedron”. You probably already know where I’m going. It didn’t work out for those most uncomplicated cases. It turned out that I optimised the algorithm and its parameters that only worked for this particular model. For others, it was wrong. As a consequence, the thesis defence was delayed by a year.</p> <p>Of course, situations like that are more than ordinary. For example, in the Uber self-driving car, <a href="https://www.bbc.com/news/technology-54175359">developers forgot to handle the situation when someone runs the red light</a>.</p> <p>There are more categories for the AI failures, such as <a href="https://www.unite.ai/ai-generated-language-is-beginning-to-pollute-scientific-literature/">self-written research papers that link each other in a bibliography</a>. The consequence is littering scientific works, reducing trust in them and making it difficult to enter the topic.</p> <p>A 2018 <a href="https://www.gartner.com/en/newsroom/press-releases/2018-02-13-gartner-says-nearly-half-of-cios-are-planning-to-deploy-artificial-intelligence">Gartner report</a> predicted that through 2030, 85% of AI projects will provide false results caused by bias that has been built into the data or the algorithms.</p> <p>Do I want to scare you and say that <em>AI is fake</em>? Not at all. Artificial intelligence can be excellent support for people in making decisions. It can speed up data processing and reduce tedious work (yes, <a href="https://venturebeat.com/2021/07/18/openai-codex-shows-the-limits-of-large-language-models/">also programming</a>). The risk, however, comes when we blindly believe in the results.</p> <p>We can trust the algorithm as much as we can trust the person training it.</p> <p>We, programmers, are considered magicians by other people. Our families usually don’t know what we are doing, except “doing something with computers”. Artificial Intelligence is a topic difficult even for us. You can imagine how hard it is for others.</p> <p><strong>What can be done about this?</strong> Explain to people the basic principles of artificial intelligence. Explain that this is not the fourth person of the Godhead or the only one righteous like the characters played by Clint Eastwood. Humans are often wrong, and so is AI. Algorithms will only learn as much as we teach them. They will convey our biases and our worldview. They will be just as fair as we are.</p> <p>Everything in the algorithms depends on the proper selection of:</p> <ul> <li>algorithm parameters,</li> <li>a set of input data,</li> <li>test data, where we say whether black is black and white is white.</li> </ul> <p>Each of these elements is prone to error.</p> <p>Therefore, when the computer says no, verify if it’s doing it right.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. read also my thoughts on whether <a href="/en/chat_gpt_revolution_or_not/">ChatGPT is a revolution or not</a>.</p><![CDATA[Anti-patterns in event modelling - Property Sourcing]]>https://event-driven.io/en/property-sourcing/https://event-driven.io/en/property-sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8924b6f6d069fe623500a139b3719833/d2429/2021-08-18-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACf0lEQVQozwXBV1PaAAAA4Lz02tfeecV61IEglb2RQAQSIIMVhMRAgpCmLBkCyghhVgO12OrVczzZv9rvA4wWHZcM8nmaFYryav1z/XAlTiez5Y/l79Xd4/Prv0azei1PX99eH5+exzO5OxgPpdlkfn05lIBo0Ob3W3zHphCJ1CtZgoqfcieCUJBGo2brIpHAYARsDbvDSf/l7eX+7++b5a/JXO6PZpOFDJCIyR+wqb/uZyk4moID4WMqGYBhV5ZLRQg4EHCRKWK1Xl/12pPVdL4QG+3G9VSU71bS4gbw+mxOl9VoNjA5Ioy5QxE4EYMDESidibu9TtBrSZ/Elg93t0/3N7cLWV4s7/90Bx2+lh0sp4DVZUZAhxsCSRLzHFmIeLhYPCGpBMsxfFXI8vmzAiNK7bbUvxK70uiivxh9E4qVUq63GAF2h8rs1KFRCMe94RB0mkEzNAGF/HgYFM6/j8VZg883h51StTCeDiSp1xt0i4XMGZcut2qA3mkwOC0YgZIEBAVdDB1BYIfBZtTrVKl4tCHWsUw8k02XOFbgqHKpcCvPhBLLFqg8zwIohtmPrIkkiqKBMOJlTqN2t0Vv1Bith0WeH89H5+dlsde9bFbr5UK9Vbuo8cJZhsB9yRQOHBzq9AcqyKnx2NR2uyHsd2u16l31tsGwT6BYhuFACEQJJEJF8GSMLzIcjWdxTxDxxGNhYMd44HCZAkG7w31oMmujQYvDqt7Z37EYNSxLD/vtHIO3Rp3OdEgmETQC0jTBs0mKxoiQB1AoN7d3t/b2FIqtDYXio1K5+enzxof3774oN6OJUKfE0jEvGotUKkI2F03FfSQBJVCvx6rRqhT/AVon2bdtel9cAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/8924b6f6d069fe623500a139b3719833/a331c/2021-08-18-cover.png" srcset="/static/8924b6f6d069fe623500a139b3719833/36ca5/2021-08-18-cover.png 200w, /static/8924b6f6d069fe623500a139b3719833/a3397/2021-08-18-cover.png 400w, /static/8924b6f6d069fe623500a139b3719833/a331c/2021-08-18-cover.png 800w, /static/8924b6f6d069fe623500a139b3719833/d2429/2021-08-18-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>The first time I got down to work at Event Sourcing, I was very energized. Book knowledge almost fell out of me. However, when I sat down to programming, I was looking like <a href="https://miro.medium.com/max/1200/1*snTXFElFuQLSFDnvZKJ6IA.png">the dog from meme</a>.</p> <p>This is usually the case when we realize that putting theory into practice is not as easy as it may seem.</p> <p>When we learn a new pattern, we subconsciously try to translate our previous habits into it. It is a human way of getting familiar with the unknown. Looking for similarities isn’t bad by itself. It allows us to move forward. Worse, when we stick to these clichés and kill our curiosity. Each pattern can become an anti-pattern when used in a context other than it was invented. When you hold a hammer in your hand, it is not difficult to see nails in everything.</p> <p>When we start modelling our system with events, we can easily fall into the trap. We are used to looking at our functionalities from the perspective of the data model. When you hold a relational database in your hands, you will see tables everywhere. Because we have read that <a href="/en/events_should_be_as_small_as_possible/">events should be as small as possible</a> the first idea for an event can be, e.g. <em>FirstNameChanged</em>. Another <em>LastNameChanged</em>, etc. We also see the use immediately, that is, a straight history of changes to our entity. Those events may look as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FirstNameChanged</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> FirstName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> ChangedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">FirstNameChanged</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> firstName<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> changedAt<span class="token punctuation">)</span> <span class="token punctuation">{</span> FirstName <span class="token operator">=</span> firstName<span class="token punctuation">;</span> ChangedAt <span class="token operator">=</span> changedAt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LastNameChanged</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> LastName<span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> ChangedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">LastNameChanged</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> lastName<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> changedAt<span class="token punctuation">)</span> <span class="token punctuation">{</span> LastName <span class="token operator">=</span> lastName<span class="token punctuation">;</span> ChangedAt <span class="token operator">=</span> changedAt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We may also come up with brilliant the idea of having both previous and new ones. Then we could directly create a history of changes to an audit trail in the UI.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">FirstNameChanged</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> PreviousFirstName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> NewFirstName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> ChangedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">FirstNameChanged</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> previousFirstName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> newFirstName<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> changedAt<span class="token punctuation">)</span> <span class="token punctuation">{</span> PreviousFirstName <span class="token operator">=</span> previousFirstName<span class="token punctuation">;</span> NewFirstName <span class="token operator">=</span> newFirstName<span class="token punctuation">;</span> ChangedAt <span class="token operator">=</span> changedAt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">LastNameChanged</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> PreviousLastName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> NewLastName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> ChangedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">LastNameChanged</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> previousLastName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> newLastName<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> changedAt<span class="token punctuation">)</span> <span class="token punctuation">{</span> PreviousLastName <span class="token operator">=</span> previousLastName<span class="token punctuation">;</span> NewLastName <span class="token operator">=</span> newLastName<span class="token punctuation">;</span> ChangedAt <span class="token operator">=</span> changedAt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Even by looking at those events, we can sniff the unpleasant smell. It’s easily visible that this approach will not be maintainable. When our model grows, we’ll get many tiny, copy/pasted, meaningless events.</p> <p>The critical aspect of event modelling is to have them close to the business. Events should correspond directly to the result of business operations in the system. To achieve this, the event should be derived from a specific request/command processing. Based on the values ​​sent in the command, we know what data was transferred. Based on them and the business logic, we can fill in the event data.</p> <p>The fact that the name has changed is not usually a factor affecting the business logic. Usually, we just accept the change, fill in the data, and that’s it. Therefore, we can pass this information in an event and use it in a projection to build a read model. However, even if we have a Jiralike form to edit a specific field, it is worth grouping such changes according to characteristics.</p> <p>We can, among others, have <em>PersonalDataUpdated</em> event triggered by updating first name, last name, etc. These fields may have the <em>Option</em> type to check if they have been changed. An example implementation of such a type in C# might look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">struct</span> <span class="token class-name">Option <span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Option <span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> None <span class="token operator">=></span> <span class="token keyword">default</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Option <span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> Some <span class="token punctuation">(</span><span class="token class-name">T</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Option <span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token punctuation">(</span><span class="token keyword">value</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">readonly</span> <span class="token class-name"><span class="token keyword">bool</span></span> isSome<span class="token punctuation">;</span> <span class="token keyword">readonly</span> <span class="token class-name">T</span> <span class="token keyword">value</span><span class="token punctuation">;</span> Option <span class="token punctuation">(</span><span class="token class-name">T</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token keyword">value</span> <span class="token operator">=</span> <span class="token keyword">value</span><span class="token punctuation">;</span> isSome <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token keyword">value</span> <span class="token keyword">is</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">bool</span></span> IsSome <span class="token punctuation">(</span><span class="token keyword">out</span> <span class="token class-name">T</span> <span class="token keyword">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">value</span> <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token keyword">value</span><span class="token punctuation">;</span> <span class="token keyword">return</span> isSome<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>then the usage as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">PersonalDataUpdated</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Option<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> FirstName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Option<span class="token punctuation">&lt;</span><span class="token keyword">string</span><span class="token punctuation">></span></span> LastName <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> ChangedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">PersonalDataUpdated</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> firstName<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> lastName<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> changedAt<span class="token punctuation">)</span> <span class="token punctuation">{</span> FirstName <span class="token operator">=</span> firstName<span class="token punctuation">;</span> LastName <span class="token operator">=</span> newLastName<span class="token punctuation">;</span> ChangedAt <span class="token operator">=</span> changedAt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token class-name"><span class="token keyword">var</span></span> onlyLastNameUpdated <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">PersonalDataUpdated</span><span class="token punctuation">(</span> Option<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">></span><span class="token punctuation">.</span>None<span class="token punctuation">,</span> Option<span class="token operator">&lt;</span><span class="token keyword">string</span><span class="token operator">></span><span class="token punctuation">.</span><span class="token function">Some</span><span class="token punctuation">(</span><span class="token string">"Smith"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>With this, we do not have hundreds of minor events but business-significant events. They still contain details of what has changed. We can create the read models based on them. Suppose we want to build an audit trail with information about the previous and the current value. In that case, we can retrieve the last state of the model in the projection, compare it with changes in events and save the difference as a new line.</p> <p><strong>Publishing events like <em>LastNameChanged</em> is called <em>Property Sourcing</em>.</strong> This is an anti-pattern. The events themselves tell us nothing about the operation that performed them. They have no business value. Due to the number of event types we have to generate, it is also challenging to manage them. It’s also not convenient for other modules to consume them.</p> <p>Of course, sometimes it makes sense to create field change events. For example, <em>EmailUpdated</em>, <em>MaritialStatusChanged</em>, <em>AccountBalanceUpdated</em>, <em>InvoiceNumberSet</em>. These are significant business fields and can trigger other workflows.</p> <p>The basis of good event modelling is in cooperation with business. Discussion and understanding what we want to achieve is the foundation. Of course, sometimes, it is worth cutting the design discussions and getting coding. When we see them in action, it’s more accessible to find the weak spots. Still, we should not try to save the four hours of discussion time by two weeks of coding.</p> <p>It’s also important to not treat our initial event model as set in stone. We should embrace that our model will change. We’ll understand our domain better. The business will also change as time goes. We should continue to drill down and make our event model closer to the real world.</p> <p><strong>Read also other article in Anti-patterns in event modelling series:</strong></p> <ul> <li><a href="/en/state-obsession/">State Obsession</a>,</li> <li><a href="/en/i_will_just_add_one_more_field/">I’ll just add one more field</a>.</li> <li><a href="/en/clickbait_event/">Clickbait event</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If our events are only about the fields updates. If they are not related to the specific business operations. In that case, we should evaluate if simple CRUD wouldn’t be better for us (read more on that in <a href="/en/when_not_to_use_event_sourcing/">“When not to use Event Sourcing?”</a>).</p><![CDATA[How to build and push Docker image with GitHub actions?]]>https://event-driven.io/en/how_to_buid_and_push_docker_image_with_github_actions/https://event-driven.io/en/how_to_buid_and_push_docker_image_with_github_actions/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/dc7d71645c27abdf1fb0334e550d2dbd/d2429/2021-08-11-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACQUlEQVQoz3WTXUjTURiHd1O3Id0IQUZXGXXlTVcRaKXQB9EHJQhZVNgHBMUCL4IQQaX8qKbpbOgyydIyyiw/EtvENcyv2pq5smkTMyfTbef8t8me+C9XDteBHwdezvs7z/ue82qklMQkhIjbWVKwTHmpHp7DOPCDR44FGvsd2McnCIdDkUBAqGcjQsjIck5EEzOImQSEIKhI5nySYddPTrVPkWScJ/nWJzY+mEFzsgpd0yvUtej3R0QURiCkjKj5moRkIUmzdYyU2i9sLrayzeBkS9Ug6zIvsCYjj+KGl5gGHRHPvBdFidLFE66kVOmaR76R1jJLap2bVKObrQ2TrM8tZW1GHppdZ0nad5kr9Z08NNkI+H1xMPGEUnLJ/JFDxj521E+QcnOETXoX2+ucJB/RsuF4Pml5JezU1rBH18bR1xYmvT7Cyp9WrTJUg5V2F/mtJq5WPyfX0MfBsk6ySjvYrbOSVWEmp9HGgdpR0gtfUD70FU9AoCQiVM3Um7rdvzjTPMCJexZyytq4ZujgYvUbrj+xcqPJzP6yXtKLusi+380dk41wSIl71OUe/jN0en1o295zWv+Oc3oTmYXt7L07Qo7ewnmDicyKAbL1PZTbv+PwLPwtdxVhTCFFodc9S4V1lJK3VrQtPRQ8M1PUauHY7S4O1/ZR0G+jd9pDUIo4usTfZtnU7lnk6cQM9c5pKocc1Ax+RvdhjMfjkzjmFwn+Zxg0iaZE1ZIio+V4/QFm/YIZv8ArJEFFicZXkq0k/A2tmO8mM1AutQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/dc7d71645c27abdf1fb0334e550d2dbd/a331c/2021-08-11-cover.png" srcset="/static/dc7d71645c27abdf1fb0334e550d2dbd/36ca5/2021-08-11-cover.png 200w, /static/dc7d71645c27abdf1fb0334e550d2dbd/a3397/2021-08-11-cover.png 400w, /static/dc7d71645c27abdf1fb0334e550d2dbd/a331c/2021-08-11-cover.png 800w, /static/dc7d71645c27abdf1fb0334e550d2dbd/d2429/2021-08-11-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In the <a href="/en/how_to_buid_an_optimal_docker_image_for_your_application">previous post</a>, I explained that with a few simple tricks, you can make your Docker image less cluttered and build faster. I explained practical patterns on how to do that.</p> <p>This time I’ll take a step forward and explain how to publish the image to the Docker registry. It’s a place in which you can store your Docker images. You can use them to share images with your team or deploy them to your hosting environment (e.g. Kubernetes, or another container hosting). We’ll use GitHub Actions as an example. It has a few benefits:</p> <ul> <li>it’s popular and easy to set up,</li> <li>it’s free for Open Source projects,</li> <li>by design, it integrates easily with GitHub tools like <a href="https://github.com/features/packages">GitHub Container Registry</a>.</li> </ul> <p>It’s a quickly evolving, decent tool. Of course, it’s not perfect. Documentation is not great, but that’s also the reason why I’m writing this post.</p> <p>We’ll use two popular Docker registries:</p> <ul> <li><a href="https://hub.docker.com/">Docker Hub</a>: the default one, provided by Docker. It’s commonly used for the public available images. If you run <em>docker pull</em>, it’ll try to load the image from it by default. However, from November 2020, it has <a href="https://www.docker.com/blog/docker-hub-image-retention-policy-delayed-and-subscription-updates/">significant limits for free accounts</a>.</li> <li><a href="https://github.com/features/packages">GitHub Container Registry (GHCR)</a>: GitHub introduced its container registry as a Packages service spin-off (you can use it to host artefacts like NPM, NuGet packages, etc.). It allows both public and private hosting (which is crucial for commercial projects).</li> </ul> <p>Before we push images, we need to do a basic setup for the container registry:</p> <h2 id="docker-hub-publishing-setup" style="position:relative;"><a href="#docker-hub-publishing-setup" aria-label="docker hub publishing setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Docker Hub publishing setup</h2> <ol> <li>Create an account and sign in to <a href="https://hub.docker.com">Docker Hub</a>.</li> <li>Go to Account Settings => Security: <a href="https://hub.docker.com/settings/security">link</a> and click <strong>New Access Token</strong>.</li> <li>Provide the name of your access token, save it and copy the value (you won’t be able to see it again, you’ll need to regenerate it).</li> <li>Go to your GitHub secrets settings (Settings => Secrets, url <a href="https://github.com/%7Byour_username%7D/%7Byour_repository_name%7D/settings/secrets/actions">https://github.com/{your_username}/{your_repository_name}/settings/secrets/actions</a>).</li> <li>Create two secrets (they won’t be visible for other users and will be used in the non-forked builds)</li> </ol> <ul> <li><em>DOCKERHUB_USERNAME</em> - with the name of your Docker Hub account (do not mistake it with GitHub account)</li> <li><em>DOCKERHUB_TOKEN</em> - with the pasted value of a token generated in point 3.</li> </ul> <h2 id="github-container-registry-publishing-setup" style="position:relative;"><a href="#github-container-registry-publishing-setup" aria-label="github container registry publishing setup permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Github Container Registry publishing setup</h2> <ol> <li><a href="https://docs.github.com/en/packages/guides/enabling-improved-container-support">Enable GitHub Container Registry</a>. Profile => Feature Preview => Improved Container Support => Enable.</li> <li>Create <a href="https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token">GitHub Personal Access Token</a> in your profile <a href="https://github.com/settings/tokens">developer settings page</a>. Copy the value (you won’t be able to see it again, you’ll need to regenerate it). Select at least following scopes:</li> </ol> <ul> <li><em>repo</em></li> <li><em>read:packages</em></li> <li><em>write:packages</em></li> </ul> <ol start="4"> <li>Go to your GitHub secrets settings (Settings => Secrets, url <a href="https://github.com/%7Byour_username%7D/%7Byour_repository_name%7D/settings/secrets/actions">https://github.com/{your_username}/{your_repository_name}/settings/secrets/actions</a>).</li> <li>Navigate to your package landing page <a href="https://github.com/%7Byour_username%7D/%7Byour_repository_name%7D/pkgs/container/%7Byour_package_name%7D">https://github.com/{your_username}/{your_repository_name}/pkgs/container/{your_package_name}</a>. Grant GitHub action write access (more in <a href="https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-ghcrio">GitHub Registry docs</a>). By that, as long as user running action has proper permissions (we can also setup them to automatically derive them from repository config) we can use default <a href="https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret"><em>GITHUB_TOKEN</em> secret</a>. Thanks, @Brickwall, for pointing that out in the <a href="https://event-driven.io/en/how_to_buid_and_push_docker_image_with_github_actions/#comment-5915972111">comment</a>!</li> </ol> <p>Once we have Docker registries setup, we can create a workflow file. It should be located in the <em>..github\workflows</em> directory in our repository. Let’s name it <em>build-and-publish.yml</em>.</p> <p>We’ll run this pipeline when Pull Request is created and on the main branch. We’ll be pushing the Docker image only on the main branch because we don’t want to spam the registry with intermediate images. If you want to, e.g. run manual tests for the pull request branch - you may also consider publishing also prerelease packages.</p> <p>The process will look as follows:</p> <ol> <li>Use working directory where Dockerfile is located (e.g. <em>src</em>)</li> <li>Checkout code.</li> <li>Log in to DockerHub and GHCR using credentials set up in the previous step.</li> <li>Build Docker image.</li> <li>Publish Docker image if the pipeline is running on the main branch.</li> </ol> <p>The pipeline has to be run on the Linux machine, as Windows and macOS lack Docker configuration.</p> <p>The resulting file will look as follow:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Build and Publish <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token comment"># run it on push to the default repository branch</span> <span class="token key atrule">push</span><span class="token punctuation">:</span> <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>main<span class="token punctuation">]</span> <span class="token comment"># run it during pull request</span> <span class="token key atrule">pull_request</span><span class="token punctuation">:</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token comment"># define job to build and publish docker image</span> <span class="token key atrule">build-and-push-docker-image</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build Docker image and push to repositories <span class="token comment"># run only when code is compiling and tests are passing</span> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token comment"># steps to perform in job</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Checkout code <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v4 <span class="token comment"># setup Docker buld action</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Set up Docker Buildx <span class="token key atrule">id</span><span class="token punctuation">:</span> buildx <span class="token key atrule">uses</span><span class="token punctuation">:</span> docker/setup<span class="token punctuation">-</span>buildx<span class="token punctuation">-</span>action@v2 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Login to DockerHub <span class="token key atrule">uses</span><span class="token punctuation">:</span> docker/login<span class="token punctuation">-</span>action@v2 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">username</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.DOCKERHUB_USERNAME <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">password</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.DOCKERHUB_TOKEN <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Login to Github Packages <span class="token key atrule">uses</span><span class="token punctuation">:</span> docker/login<span class="token punctuation">-</span>action@v2 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">registry</span><span class="token punctuation">:</span> ghcr.io <span class="token key atrule">username</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> github.actor <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">password</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.GITHUB_TOKEN <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build image and push to Docker Hub and GitHub Container Registry <span class="token key atrule">uses</span><span class="token punctuation">:</span> docker/build<span class="token punctuation">-</span>push<span class="token punctuation">-</span>action@v2 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token comment"># relative path to the place where source code with Dockerfile is located</span> <span class="token key atrule">context</span><span class="token punctuation">:</span> ./src/samples/simple <span class="token comment"># Note: tags has to be all lower-case</span> <span class="token key atrule">tags</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string"> oskardudycz/eventsourcing.nodejs.simple:latest ghcr.io/oskardudycz/eventsourcing.nodejs/simple:latest</span> <span class="token comment"># build on feature branches, push only on main branch</span> <span class="token key atrule">push</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> github.ref == 'refs/heads/main' <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Image digest <span class="token key atrule">run</span><span class="token punctuation">:</span> echo $<span class="token punctuation">{</span><span class="token punctuation">{</span> steps.docker_build.outputs.digest <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre></div> <p>As you see the pipeline is technology agnostic. You can reuse it in whatever technology you choose. This gives a lot of possibilities to simplify pipelines and processing.</p> <p>Read also other articles around DevOps process:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/marten_and_docker/">How to create a Docker image for the Marten application</a></li> <li><a href="/en/docker_compose_profiles/">Docker Compose Profile, one the most useful and underrated features</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[How to build an optimal Docker image for your application?]]>https://event-driven.io/en/how_to_buid_an_optimal_docker_image_for_your_application/https://event-driven.io/en/how_to_buid_an_optimal_docker_image_for_your_application/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/389b5a65a50d4ab85d1dbf235c1bc88d/d2429/2021-08-04-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABqUlEQVQoz3XST08TQRQA8CZ8Ca98AG5+CT8AMYSjJgpJm7RUNPFidKU3TURjAk0DFA9bNtUGQkAOpAmE2GglaICstOHfQlHb7tqZ92be7O6oJCQU2nd+v/dn5kWwSzAOLQ4MkAEgIiC6IER7TuQ64/x/dugrrWSILCAEgDpDhynW7tswAAKKQEmSYqG8+3R5c3a3UbSdUBEn32nRedkuWAoBzFspVbWigTeFnuTszXz11vudzzv2kXPa4EK0Dx656AmBT9v2wePMt9HMvrn29Ub8dWTQ6I1N9Ke3707sJaZrK1+OSQmA6xhRCOG6TbPopAp8KL3ZN2Ldfl5Mmmp0Tj/KhzFLp3IV3nKFkB3GBkAi6cs/qXwtmtPj87XMwvfEnE6Y/khOxS1tmJWW1+yMz/sLZN7LfCVq6qQVDI+VYlnvwbx++EHfy6p3H+2QOIcuD8YBQp8+bVXuT54Zq3pmvPDkzqv425Noum6YByenNaUILi199Z8BURGul38sbhxu2J6VXTKGn029yDZcj6S8LDsfCQCGAdV//6zuHx/+cs8aTSlRSroi/+G/3rdDsG7/OpgAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/389b5a65a50d4ab85d1dbf235c1bc88d/a331c/2021-08-04-cover.png" srcset="/static/389b5a65a50d4ab85d1dbf235c1bc88d/36ca5/2021-08-04-cover.png 200w, /static/389b5a65a50d4ab85d1dbf235c1bc88d/a3397/2021-08-04-cover.png 400w, /static/389b5a65a50d4ab85d1dbf235c1bc88d/a331c/2021-08-04-cover.png 800w, /static/389b5a65a50d4ab85d1dbf235c1bc88d/d2429/2021-08-04-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Some time ago, I realised that my posts from 2011 about configuration and operation in SCRUM using TFS are still hanging on the Internet. Actually, I shouldn’t really brag about it, because nowadays it can be a bit embarrassing. However, that’s a decent confirmation that I was interested since the beginning in Continuous Integration and Delivery.</p> <p>I have come across a lot of CI/CD platforms throughout my career. Starting with Hudson (now renamed Jenkins), TFS, Bamboo (yup…), Jenkins, Azure DevOps, GitHub Actions etc. Since the beginning, nothing irritated me as much as manually copying files to FTP. I always hated running some magic scripts praying for luck. When I started out in 2007, times were wild. Some say that <em>“It used to be better and devs were more skilled. You had to know how to write scripts and how deployment works.”</em>. From my perspective, it’s better that you might not need to know it now because tools are better. In my opinion, the more repetitive, boring activities you have to perform manually, the greater the chance that you will make a mistake.</p> <p>What do my inclinations for event-based architectures and CI/CD have in common? Focus on the essence, so making sure the system works in the right way. Both in terms of business and technology. The more we automate, the more we can focus on delivering business value.</p> <p>Today, I would like to share a Docker build configuration for my Event Sourcing sample repositories. I’ll explain it based on the .NET and NodeJS applications. I think that the three aspects that I used there may be helpful to you as well:</p> <ol> <li>Layers and why you should remember about them.</li> <li>Multi-stage build.</li> <li>Deploying the image to the repository (this is explained in the <a href="/en/how_to_buid_and_push_docker_image_with_github_actions">dedicated post</a>).</li> </ol> <p>In this post, I’ll focus on the most important things for the developer, so pardon me, simplifications made for brevity.</p> <p><strong>Container image definition</strong>: what is in the <em>DOCKERFILE</em>. We can compare it to the instructions for assembling a table. We describe what steps must be performed to put individual elements together.</p> <p><strong>Container image</strong> is an already assembled table that is somewhere in the warehouse. Downloading the Docker image (via <em>docker pull</em> command) can be compared to delivering the table from the warehouse to your home. Launching (<em>docker run</em>) can be compared to unpacking this table and using it.</p> <p>What’s important (and indirectly connected with Event Sourcing) is that everything in containers is <em>immutable</em>. Just like a table in a warehouse once packed, no one should move until the buyer opens it, and the images should not (and cannot) be modified.</p> <p>The same goes for the individual parts. If we assemble a table, it usually means that <em>“first, assemble one leg, then the second, third, fourth, top, and finally turn it into one whole”</em>. When we have glued it together or twisted it, we also try not to change it because we may not turn it back later.</p> <p>The same is true of Docker. <strong>When defining the container image, each instruction (line) creates a new <em>layer</em>.</strong> The layer can be compared to a twisted tabletop, to which we will then screw the legs.</p> <p>Why am I talking about this? Because such layers should be placed one after the other, from the least changeable to the most common. <strong>Typically, the process of building a project in Docker is:</strong></p> <ol> <li>Copy the project files.</li> <li>Start the build process.</li> <li>Use the artefacts and run the project.</li> </ol> <p>With build tools, we usually can build anything and generate artefacts with a single command. <strong>However, it’s a good idea to break these steps down into separate steps.</strong> That is:</p> <ul> <li>package installation: <em>dotnet restore</em>, <em>npm install</em>,</li> <li>building a project: <em>dotnet build</em>, <em>npm run build</em>,</li> <li>creating final artefacts: <em>dotnet publish</em>, <em>npm prune —production</em>.</li> </ul> <p>Why? Installing packages does not require the entire project. To do it, we only need the project file. Docker is so clever that it only rebuilds the layers (represented by lines in Dockerfile) in which something has changed. That can be a definition or the copied files. If we first copy the project file and run the package installation, it will only trigger this step on subsequent builds if the project file we copied has changed (i.e. we added a new package, updated its version, etc.). Thanks to this, we can cut the valuable build time.</p> <p>In .NET it will look like that:</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token instruction"><span class="token keyword">FROM</span> mcr.microsoft.com/dotnet/sdk:8.0-alpine <span class="token keyword">AS</span> builder</span> <span class="token comment"># Setup working directory for the project </span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token instruction"><span class="token keyword">COPY</span> ./Core/Core.csproj ./Core/ </span> <span class="token instruction"><span class="token keyword">COPY</span> ./Core.Marten/Core.Marten.csproj ./Core.Marten/ </span> <span class="token instruction"><span class="token keyword">COPY</span> ./Core.WebApi/Core.WebApi.csproj ./Core.WebApi/ </span> <span class="token instruction"><span class="token keyword">COPY</span> ./Sample/Tickets/Tickets/Tickets.csproj ./Sample/Tickets/Tickets/ </span> <span class="token instruction"><span class="token keyword">COPY</span> ./Sample/Tickets/Tickets.Api/ ./Sample/Tickets/Tickets.Api/ </span> <span class="token comment"># Restore nuget packages </span> <span class="token instruction"><span class="token keyword">RUN</span> dotnet restore ./Sample/Tickets/Tickets.Api/Tickets.Api.csproj</span></code></pre></div> <p>I’m copying more than one project file, as I need to have all the referenced projects to restore all packages successfully.</p> <p>NodeJS application config is similar:</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token instruction"><span class="token keyword">FROM</span> node:lts-alpine <span class="token keyword">AS</span> builder</span> <span class="token comment"># Setup working directory for project</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token instruction"><span class="token keyword">COPY</span> ./package.json ./ </span> <span class="token instruction"><span class="token keyword">COPY</span> ./package-lock.json ./ </span> <span class="token instruction"><span class="token keyword">COPY</span> ./tsconfig.json ./ </span> <span class="token comment"># install node modules</span> <span class="token comment"># use `npm ci` instead of `npm install`</span> <span class="token comment"># to install the exact versions from `package-lock.json`</span> <span class="token instruction"><span class="token keyword">RUN</span> npm ci</span></code></pre></div> <p>If we change any file in the project, this fragment will not be rerun. Docker will use the previously generated layer.</p> <p><strong>The next step is to copy the project files and run the build.</strong></p> <p>The key here is to ensure that our build command won’t install dependencies automatically again, <em>dotnet build</em> does that. However, we can run it with <em>---no-restore</em> parameter, which will prevent packages from being downloaded again.</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token comment"># Copy project files</span> <span class="token instruction"><span class="token keyword">COPY</span> ./Core ./Core</span> <span class="token instruction"><span class="token keyword">COPY</span> ./Core.Marten ./Core.Marten</span> <span class="token instruction"><span class="token keyword">COPY</span> ./Core.WebApi ./Core.WebApi</span> <span class="token instruction"><span class="token keyword">COPY</span> ./Sample/Tickets/Tickets ./Sample/Tickets/Tickets</span> <span class="token instruction"><span class="token keyword">COPY</span> ./Sample/Tickets/Tickets.Api ./Sample/Tickets/Tickets.Api </span> <span class="token comment"># Build project with Release configuration</span> <span class="token comment"># and no restore, as we did it already</span> <span class="token instruction"><span class="token keyword">RUN</span> dotnet build -c Release --no-restore ./Sample/Tickets/Tickets.Api/Tickets.Api.csproj</span></code></pre></div> <p>In NodeJS this will look similar:</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token comment"># Copy project files </span> <span class="token instruction"><span class="token keyword">COPY</span> src ./src</span> <span class="token comment"># Build project</span> <span class="token instruction"><span class="token keyword">RUN</span> npm run build:ts</span></code></pre></div> <p><strong>Finally, we should prepare the final artefacts.</strong> Again we don’t want to repeat ourselves. While creating the deployment package, we don’t want to build source codes again.</p> <p>In .NET, while running <em>dotnet publish</em>, we have to add the <em>—no-build</em> option to achieve that.</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token comment"># Publish project to output folder </span> <span class="token comment"># and no build, as we did it already </span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app/Sample/Tickets/Tickets.Api </span> <span class="token instruction"><span class="token keyword">RUN</span> dotnet publish -c Release --no-build -o out</span></code></pre></div> <p>In NodeJS, it’s a bit different, as we’re not generating the result binaries, but <em>just</em> cleaning the unused dependencies.</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token comment"># sets environment to production</span> <span class="token comment"># and removes packages from devDependencies </span> <span class="token instruction"><span class="token keyword">RUN</span> npm prune --production</span></code></pre></div> <p>In theory, we could release such a Docker image. However, besides the result packages, it would contain source codes and all the build tools we used. Typically images that have the development SDK preinstalled is heavier. That means that downloading and using it takes more resources. We’d like to optimise that.</p> <p><strong>Fortunately, Docker can do multi-stage build</strong>. What is it that? In short, by using <em>FROM</em>, we’re telling what image should be our <em>base image</em>. Such an image simply defines what we will already have installed and configured on our system.</p> <p>We can have more than one <em>FROM</em> in our build definition. When we do that, we will create another image defined <em>“on the side”</em>. What’s essential is that it has access to all the others images that we created earlier.</p> <p>So we can use a heavier image from the SDK, generate artefacts in it, and then use a lighter image with a minimal number of dependencies (e.g. only runtime). Thanks to this, we can copy the previously generated files to it.</p> <p>This is what the final definition looks like for .NET</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token comment"># the first, heavier image to build your code</span> <span class="token instruction"><span class="token keyword">FROM</span> mcr.microsoft.com/dotnet/sdk:8.0-alpine <span class="token keyword">AS</span> builder</span> <span class="token comment"># (...)</span> <span class="token comment"># second, final, lighter image</span> <span class="token instruction"><span class="token keyword">FROM</span> mcr.microsoft.com/dotnet/aspnet:8.0-alpine</span> <span class="token comment"># Setup working directory for the project </span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token comment"># Copy published in previous stage binaries </span> <span class="token comment"># from the `builder` image</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">builder</span></span> /app/Sample/Tickets/Tickets.Api/out . </span> <span class="token comment"># Set URL that App will be exposed </span> <span class="token instruction"><span class="token keyword">ENV</span> ASPNETCORE_URLS=<span class="token string">"http://*:5000"</span> </span> <span class="token comment"># sets entry point command to automatically </span> <span class="token comment"># run application on `docker run` </span> <span class="token instruction"><span class="token keyword">ENTRYPOINT</span> dotnet Tickets.Api.dll</span></code></pre></div> <p>For NodeJS, we can use the same image, as we didn’t use additional build tools. However, we still benefit from having only the result files in the final image.</p> <div class="gatsby-highlight" data-language="dockerfile"><pre class="language-dockerfile"><code class="language-dockerfile"><span class="token comment"># the first image to build your code</span> <span class="token instruction"><span class="token keyword">FROM</span> node:lts-alpine <span class="token keyword">AS</span> builder</span> <span class="token comment"># (...)</span> <span class="token comment"># second, final, lighter image without source code and build dependencies</span> <span class="token instruction"><span class="token keyword">FROM</span> node:lts-alpine</span> <span class="token comment"># Setup working directory for the project</span> <span class="token instruction"><span class="token keyword">WORKDIR</span> /app</span> <span class="token comment"># Copy published in previous stage binaries</span> <span class="token comment"># from the `builder` image</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">builder</span></span> /app/dist ./dist</span> <span class="token instruction"><span class="token keyword">COPY</span> <span class="token options"><span class="token property">--from</span><span class="token punctuation">=</span><span class="token string">builder</span></span> /app/node_modules ./node_modules</span> <span class="token comment"># Set URL that App will be exposed</span> <span class="token instruction"><span class="token keyword">EXPOSE</span> 5000</span> <span class="token comment"># sets entry point command to automatically</span> <span class="token comment"># run application on `docker run`</span> <span class="token instruction"><span class="token keyword">ENTRYPOINT</span> [<span class="token string">"node"</span>, <span class="token string">"./dist/index.js"</span>]</span></code></pre></div> <p>See the final images in my sample repos:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/blob/main/Dockerfile">.NET</a></li> <li><a href="https://github.com/oskardudycz/EventSourcing.NodeJS/blob/main/samples/simple/Dockerfile">NodeJs</a></li> </ul> <p>By using <em>ENTRYPOINT</em> we tell Docker to start our project as the default command. It’s also one of the best practices for someone to just do a <em>docker run</em> and start the project executable with the default settings.</p> <p>The image built in this way can be used to implement and run in the final environment. To do this, we need to push it into the repository. The default and the most popular is DockeHub. The other popular is GitHub Container Registry.</p> <p>We can also use such an image for manual and automated tests.</p> <p><strong>To sum up - thanks to a few simple tricks, we can make the build time significantly shorten</strong>. In addition, it won’t contain unnecessary files and dependencies. Thanks to that, downloading it and launching it will be faster. As you see, these rules can be applied to any platform. I showed .NET and NodeJS as an example, but the same could be done in other environments.</p> <p>I encourage you to also read the follow-up articles <a href="/en/how_to_buid_and_push_docker_image_with_github_actions">“How to build and push Docker image with GitHub actions?”</a> and <a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a>.</p> <p>Read also other articles around DevOps process:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/marten_and_docker/">How to create a Docker image for the Marten application</a></li> <li><a href="/en/docker_compose_profiles/">Docker Compose Profile, one the most useful and underrated features</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[Notes about C# records and Nullable Reference Types]]>https://event-driven.io/en/notes_about_csharp_records_and_nullable_reference_types/https://event-driven.io/en/notes_about_csharp_records_and_nullable_reference_types/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/6ea75b2e171f02a9f3032ccb64aafe46/d2429/2021-07-28-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACaklEQVQozwXBW0/TUAAA4P4CY8IiyS64rvfLac9Ze9q13WnLOraObaJAYBuoCEwE5DKYygjEKEHQOIhCQvTB65svPvgH/T7q41N2KnRkye/v7x4fHTAZfU1mVkwEtEiGTjal6bqXzlprBPxpM+dV5k5CG00iEU5AEFAXG8L2fS6RQCqHJBCsavJNWflVEcsCtp7US1sPyGZdVZy4NlOGyFZwtyZs1nOubgmcSw0WZCTjbFrLJgFNWzVo/aio554gpFVGQKTbAKUojma+XF3EtbkASr9fyX9f0qtN01R8iuV8qHgFI1Q47NqVerOFJQsKViYDM8m8wVpN05HEYr/3oj23eOs2d9AWz5ZorDmIQ1Q5b+kgKNoRUr1xv7GztdXv9QipSTTAkqAw+W/TuecuQ/OkKKAVkdFoM5XGMmfTY5CKdS0vWUwKynxB5G2QM21cMnVy2uUvN8ZmY/iwaL+fGBsZ1a8DcUjky0ABejhtIRf51FUjS5Atjru8jJvK5Gm03tKmDOje9IWvA7bXgZ5GAiNQ9MlDD3yPhG0bcrxnCgVDCykzK5thOX47DxAZOMs/l08+1XdpnvimMROwqlRARsmNutjtyBx2cmpqVKVZiOJQFgsUkyMiZ1sL1XwtCpXKOpwzWL+uAp3FIwkYhfHezm7oVyXWVI0JOL+nKEWj5Zf2HxQeVSknPnI1qOVk7q6hiFYyAxZU7roRvSMoxxjDs5M3x6+PBoc8g6HmQ1yTpDB4dq88mLVaZQo6S+2KTpCmR48L1UUgk2EA/nUaAwcmU3B9eeXz8MNiZ0kSbF3xMLDbEbStAIXjquT8B2BkpeMYY2krAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/6ea75b2e171f02a9f3032ccb64aafe46/a331c/2021-07-28-cover.png" srcset="/static/6ea75b2e171f02a9f3032ccb64aafe46/36ca5/2021-07-28-cover.png 200w, /static/6ea75b2e171f02a9f3032ccb64aafe46/a3397/2021-07-28-cover.png 400w, /static/6ea75b2e171f02a9f3032ccb64aafe46/a331c/2021-07-28-cover.png 800w, /static/6ea75b2e171f02a9f3032ccb64aafe46/d2429/2021-07-28-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In the last months, I spent quite some time playing with C# <a href="https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record">records</a> and <a href="https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references">Nullable Reference Types</a>. I was hoping that thanks to them, I would trust my type better than before. I hoped that records will be good for Value Objects. So, if I created an object, it is immutable and meets the defined rules. From NRT, I expected if a variable type tells me it’s not null, then it really is not. What’s the result of my investigation?</p> <p>Let’s start with a simple record to represent a money transfer:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">MoneyTransfer</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Amount<span class="token punctuation">,</span> <span class="token class-name">Guid</span> FromAccountId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ToAccountId<span class="token punctuation">,</span> <span class="token comment">// should always be specified and never be null</span> <span class="token class-name"><span class="token keyword">string</span></span> Title<span class="token punctuation">,</span> <span class="token comment">// optional, nullable string</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Comment <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span></code></pre></div> <p>An example of usage looks like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> anna <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> john <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> amount <span class="token operator">=</span> <span class="token number">100</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> moneyTransfer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MoneyTransfer</span><span class="token punctuation">(</span> amount<span class="token punctuation">,</span> anna<span class="token punctuation">,</span> john<span class="token punctuation">,</span> <span class="token string">"Money laundry"</span><span class="token punctuation">,</span> <span class="token string">"Do not tell anyone!"</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>So far, everything’s great. My transfer is immutable. If I try to assign <em>null</em> value to <em>Title</em>, the compiler won’t let me. Sweet!</p> <p>OK, but how to make the record check the type rules. E.g. be sure that an amount is a positive number. Or the accounts identifiers and transfer title are not empty values?</p> <p>Personally, I like to use the pattern <a href="https://wiki.haskell.org/Smart_constructors">“Smart Constructor”</a>. C# can be modelled as a factory method that creates an object and validates the incoming values. There is no validation in the constructor because it can be used during deserialization. In this case, we just need to accept what we get.</p> <p>I could add such a method to the record definition.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">MoneyTransfer</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Amount<span class="token punctuation">,</span> <span class="token class-name">Guid</span> FromAccountId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ToAccountId<span class="token punctuation">,</span> <span class="token comment">// should always be specified and never be null</span> <span class="token class-name"><span class="token keyword">string</span></span> Title<span class="token punctuation">,</span> <span class="token comment">// optional, thanks to nullable reference types</span> <span class="token comment">// this can now be defined</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Comment <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">MoneyTransfer</span> <span class="token function">From</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">decimal</span></span> Amount<span class="token punctuation">,</span> <span class="token class-name">Guid</span> FromAccountId<span class="token punctuation">,</span> <span class="token class-name">Guid</span> ToAccountId<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Title<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Comment <span class="token operator">=</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>Amount <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>FromAccountId <span class="token operator">==</span> <span class="token keyword">default</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>ToAccountId <span class="token operator">==</span> <span class="token keyword">default</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span>Title<span class="token punctuation">.</span><span class="token function">Trim</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Length <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span><span class="token punctuation">(</span>Amount<span class="token punctuation">,</span> FromAccountId<span class="token punctuation">,</span> ToAccountId<span class="token punctuation">,</span> Title<span class="token punctuation">,</span> Comment<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Then usage will look as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> moneyTransfer <span class="token operator">=</span> MoneyTransfer<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span> amount<span class="token punctuation">,</span> Anna<span class="token punctuation">,</span> john <span class="token string">"Money laundry"</span><span class="token punctuation">,</span> <span class="token string">"Do not tell anyone!"</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>That’s better, but still not perfect. With the new syntax for records, I can create the derived object using <em>with</em> keyword. It will create a clone of the object with some properties getting new values.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> wrongMoneyTransfer <span class="token operator">=</span> moneyTransfer <span class="token keyword">with</span> <span class="token punctuation">{</span>Amount <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">100</span><span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre></div> <p>Of course, I can define a private constructor if I insist or add a validation rule for the property setter. Unfortunately, I am distancing myself more and more from the advantages that are introduced by records.</p> <p>Let’s get back to nulls. After using <em>Nullable Reference Types</em>, the compiler will not allow assigning <em>null</em> to a field not marked with a question mark. Well, almost, because I can do this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> evenWorseMoneyTransfer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MoneyTransfer</span><span class="token punctuation">(</span> amount<span class="token punctuation">,</span> Anna<span class="token punctuation">,</span> john <span class="token comment">// yes, I can force null to not null...</span> <span class="token keyword">null</span><span class="token operator">!</span><span class="token punctuation">,</span> <span class="token string">"Do not tell anyone!"</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>And I’ll assign null even though I shouldn’t be able to do so. Maybe you will say <em>“OK, nobody will do that explicitly to break the code”</em>. And that may be true of people, not necessarily for serializers. If someone sends us a request with <em>null</em> and our code expects having it defined then it may crash with Null Pointer Reference if we’re not prepared for that.</p> <p>Unfortunately, it turns out that nullable reference types are just <em>synctactic sugar</em> on top of the language. They’re only checked at compilation time. At runtime, they’re regular types where you can assign null if you’d like to.</p> <p>In summary, neither the <em>records</em> nor the <em>Nullable Reference Types</em> are entirely what they appeared in the introductory presentations. Using them reminds me of typing in TypeScript. We seem to have defined types, but it is still JavaScript underneath when we run the code. Same here, deserialization or force by a developer will let us assign <em>null</em>. I can imagine the nasty production bug made by misuse.</p> <p>My recommendation is:</p> <ol> <li>Using records as simple <em>Data Transfer Objects</em>, e.g. API requests. They are great for this.</li> <li>For types representing API requests, permanently mark all fields as nullable. We can expect anything from the user input. Then parse them into types that do offer those guarantees, applying validation in that process.</li> <li>Records are not the ideal case for the Value Objects. If you want to use simplified syntax without a constructor or factory method, you cannot use primary types. You need to send types that are validated internally for the semantic rules. Because of the <em>with</em> keyword, there cannot be rules between fields, as anyone can always replace the single property value while creating a new object.</li> </ol> <p>Using the ASP.NET endpoints for the request handling can look like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RegisterProductRequest</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> SKU<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Route</span> <span class="token punctuation">{</span> <span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token return-type class-name">IEndpointRouteBuilder</span> <span class="token function">UseRegisterProductEndpoint</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEndpointRouteBuilder</span> endpoints<span class="token punctuation">)</span> <span class="token punctuation">{</span> endpoints<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"api/products/"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> context <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FromBody</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RegisterProductRequest<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> productId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> RegisterProduct<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>productId<span class="token punctuation">,</span> sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SendCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">Created</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> endpoints<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then command handling:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">class</span> <span class="token class-name">HandleRegisterProduct</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">ICommandHandler<span class="token punctuation">&lt;</span>RegisterProduct<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>Product<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> ValueTask<span class="token punctuation">></span></span> addProduct<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>SKU<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> ValueTask<span class="token punctuation">&lt;</span><span class="token keyword">bool</span><span class="token punctuation">></span><span class="token punctuation">></span></span> productWithSKUExists<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">HandleRegisterProduct</span><span class="token punctuation">(</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>Product<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> ValueTask<span class="token punctuation">></span></span> addProduct<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>SKU<span class="token punctuation">,</span> CancellationToken<span class="token punctuation">,</span> ValueTask<span class="token punctuation">&lt;</span><span class="token keyword">bool</span><span class="token punctuation">></span><span class="token punctuation">></span></span> productWithSKUExists <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>addProduct <span class="token operator">=</span> addProduct<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>productWithSKUExists <span class="token operator">=</span> productWithSKUExists<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">ValueTask</span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">RegisterProduct</span> command<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> ct<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> product <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Product</span><span class="token punctuation">(</span> command<span class="token punctuation">.</span>ProductId<span class="token punctuation">,</span> command<span class="token punctuation">.</span>SKU<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Name<span class="token punctuation">,</span> command<span class="token punctuation">.</span>Description <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">await</span> <span class="token function">productWithSKUExists</span><span class="token punctuation">(</span>command<span class="token punctuation">.</span>SKU<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvalidOperationException</span><span class="token punctuation">(</span> <span class="token interpolation-string"><span class="token string">$"Product with SKU `</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">command<span class="token punctuation">.</span>SKU</span><span class="token punctuation">}</span></span><span class="token string"> already exists."</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">addProduct</span><span class="token punctuation">(</span>product<span class="token punctuation">,</span> ct<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">RegisterProduct</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> ProductId <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span><span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">SKU</span> SKU <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> Description <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">RegisterProduct</span><span class="token punctuation">(</span><span class="token class-name">Guid</span> productId<span class="token punctuation">,</span> <span class="token class-name">SKU</span> sku<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> description<span class="token punctuation">)</span> <span class="token punctuation">{</span> ProductId <span class="token operator">=</span> productId<span class="token punctuation">;</span> SKU <span class="token operator">=</span> sku<span class="token punctuation">;</span> Name <span class="token operator">=</span> name<span class="token punctuation">;</span> Description <span class="token operator">=</span> description<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">RegisterProduct</span> <span class="token function">From</span><span class="token punctuation">(</span><span class="token class-name">Guid<span class="token punctuation">?</span></span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> sku<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> description<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>id<span class="token punctuation">.</span>HasValue <span class="token operator">||</span> id <span class="token operator">==</span> Guid<span class="token punctuation">.</span>Empty<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>id<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">.</span><span class="token function">IsNullOrEmpty</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>description <span class="token keyword">is</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>name<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RegisterProduct</span><span class="token punctuation">(</span>id<span class="token punctuation">.</span>Value<span class="token punctuation">,</span> SKU<span class="token punctuation">.</span><span class="token function">From</span><span class="token punctuation">(</span>sku<span class="token punctuation">)</span><span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Check also more details in my other articles:</p> <ul> <li><a href="/en/how_to_validate_business_logic/">How to validate business logic</a>.</li> <li><a href="/en/explicit_validation_in_csharp_just_got_simpler/">Explicit validation in C# just got simpler!</a>.</li> </ul> <p>It may still make sense to use records, as we’re getting the automatic <em>ToString</em>, equality overloads and good looking object deconstruction.</p> <p>Both functionalities are a decent step, but unfortunately, they seem to be added hastily and not thoroughly thought out. It’s nice that they are, but we must be cautious using them as we can get into serious trouble. There are also other issue like:</p> <ul> <li>collections properties not having built-in value-comparison,</li> <li>reordering fields in Record definition can silently break our code as deconstruction is position-dependant. So compiler won’t catch that we reordered two properties of the same type.</li> </ul> <p>I predict that some of that may change in the following .NET versions, and the Records design will get more polished.</p> <p>Read also more in the article <a href="/en/generic_does_not_mean_simple">“Generic does not mean Simple”</a> and see the <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/Warehouse">full sample in my GitHub repo</a>.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[In what language are programmers writing?]]>https://event-driven.io/en/in_what_language_are_programmers_writing/https://event-driven.io/en/in_what_language_are_programmers_writing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/521b99f26d0c30794beb6c9f8937cb76/d2429/2021-07-21-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AOjfwvDly/br0eLeytbXyNXWy9vb09bZ0dPXzdbBqMCbd7avoL3GxrW8urG8vKeztZ+vtaKwtaGxtpyrsQDw5svy583u5s2zw7rJz8PU1svZ0LHOv527saHSqn7IlF+hdEyiakm2nIfMzsO/v7a4vrm3wL62wcG0v8IA7eXL8ujR6uTOz9PC5ODN2dTC18CL0LN4m2s/t39Qun1MllMojEcehlE3y8vBwsnDx8m/x8nAxsrCw8fEAPHo0/Tt2vTs2vPr2evo2uDXvtW6hNjAisWjdKtmQKpWLJhWK4lVM4xnQ7Wmkt3c0dPWztXX0NbX0dbW0QDJxrHOzrvNzLvHyLfLz8XOxKLStHfWvobKrHWYaTucYTSeYzaCVi5zYDdjRSXCt6TW2szX18rS1cjPz8EAVGdHWW1LXXFLVmxIW3NXopJjyqlqxq11vKFnwqhto4BKmnI+jWQyiGw7fWMwdWpEkZ14kZx2jJl7hJR+AGhyT3d8UnR8UnV/Vnd9V7aZX76fYr+jab+jaaqOXXJcOpB7U8KkZZJ1PnxgMoBmN1NRL1RWL0hMJ1JVMAB8b0GFfVKHelFoaEN/dUuxj1izmF+zmmG3n2elilekjF+kjF+if0WUeUZ8Yjh2YT5BNh9HOx5LRShcXkQAiXVDgHREgHJDalstmoNJsJNYrpRblXxIi3NBmIBOrI9RgGMwXEYkdFsxcVcuf2Y5al4zUUUoaF1AXmpcAJWCSZGCRmdiNG1jN5mBRYhxP2tRMH1oQYZuP4RtPZd9P3haJWlKKIVkM41vOItqMH5lMnxnQnBgQkNENgBGOxeAf1CRilajilGzmF1+ZTRfTC6Dck6bhFOMczyEbzaUfUdyWiuVai+hgEGaeD6phlGTe01eX0RcbVkbKWeo13zxXgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/521b99f26d0c30794beb6c9f8937cb76/a331c/2021-07-21-cover.png" srcset="/static/521b99f26d0c30794beb6c9f8937cb76/36ca5/2021-07-21-cover.png 200w, /static/521b99f26d0c30794beb6c9f8937cb76/a3397/2021-07-21-cover.png 400w, /static/521b99f26d0c30794beb6c9f8937cb76/a331c/2021-07-21-cover.png 800w, /static/521b99f26d0c30794beb6c9f8937cb76/d2429/2021-07-21-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="https://en.wikipedia.org/wiki/List_of_programming_languages">Wikipedia states</a> that there are around 700 programming languages. Some are more popular. Some are less. But what language do programmers write in?</p> <p>When I started my career (which was over 15 years ago), the most popular language for Polish programmers wrote was Ponglish. Ephemeral programming language. People were writing in Java, C#, etc., but typing names in Ponglish. Let’s start with a Polish language lesson:</p> <ul> <li><em>nazwa</em> - name,</li> <li><em>Użytkownik</em> - user,</li> <li><em>Czy użytkownik jest adminem?</em> - Is the user an admin?</li> <li><em>hasło</em> - password,</li> <li><em>pracownik</em> - employee.</li> </ul> <p>It was quite common to mix the Polish language with English suffixes forming names like</p> <ul> <li><em>OnNazwaUżytkownikaClick</em>,</li> <li><em>SelectImieUzytkownika</em>,</li> <li><em>ClickCzyJestAdminemCheckbox</em>.</li> <li><em>readHaslo</em></li> </ul> <p>It wasn’t a pleasure to read such a code. It doesn’t look charming, but it was due to several things:</p> <ul> <li>much weaker knowledge of the English language than now,</li> <li>foreign projects were much rarer than they are now,</li> </ul> <p>-most of the clients were Polish companies.</p> <p>On the one hand, programmers wanted to write in Polish, and on the other, stick to the convention of the programming language/framework, which was of the OnSomethingClick, SelectSomething type.</p> <p>Usually, after joining the project and seeing such code, my reaction was: “Hold me, I won’t bear that!“. I was then trying to get rid of that. Currently, it is rare to find a project where the code is not written in English. And that’s cool, but recently I started to think if it’s accurate.</p> <p>The main idea behind the Domain-Driven Design is <em>“ubiquitous language”.</em> It states that we should speak the same language as business people. That also includes naming our classes and methods according to the terms used by the business. If our client uses the term _“sending parcel”, we should use it and not replace it with, e.g. <em>“ship package”</em>. If they name _“order” a <em>“task”</em> or a <em>“job”</em> we should also adopt it. That’s cool, but how to match it all to our code when we have a Polish client? Typically you try to map it to the English name. But then there we have a question if for <em>“pracownik”</em> more appropriate is <em>“employee”</em> or <em>“worker”</em>. We may lose a lot of nuances while doing that mapping. Those nuances are usually keys to understanding business. Plus, we always need to do mental gymnastics to translate the naming back and forth while speaking to business or reading documentation written in our native language. Of course, we can build a terms dictionary translating from one language to another, but isn’t that contrary to the ubiquitous language idea? I had multiple cases in past, where:</p> <ul> <li>translation popped out in the discussions with the business. That created a lot of confusion because the business didn’t know what devs were talking about.</li> <li>devs trying to persuade the business to use their English translations to “make communication easier”.</li> <li>different naming of the same terms coming from other people doing different translations.</li> <li>mistakes and bugs because of the misunderstanding around translations.</li> </ul> <p>Anglo-Saxons have a lot easier since most programming languages ​​and the initial development of computers was mainly in the US. English has become the “lingua franca”. Domain-Driven Design is more accessible as you’re using precisely the terminology that business operates. For non-native speakers, it will always be the art of compromise. Additionally, due to blogs, reading entries, which are usually in English, the code written in Polish seems to us …strange? We could try not to mix English with our native naming. Instead of <em>IsUzytkownikAdminem</em>, we could write <em>CzyUzytkownikJestAdminem`</em>. But then we’ll have to mix it with the programming keywords that originate from English. Not to mention the fact that in Polish and many other languages <a href="https://en.wikipedia.org/wiki/Polish_phonology">we have vowels</a>. The handling of Unicode characters is still lame (create an email in Microsoft365 using any of the letters <em>ąęśćźó</em>, and you’ll cry).</p> <p>Is it only a Polish issue? It’s not. We’re not unique. I saw a few German projects with the code written in the German language. I also saw comments written in Cyrillic or Arabic. So is it so wrong to mix languages? I don’t know. I think that it’s worth at least considering. If we have a business team speaking our native language, we could try to write in our language and assess how strange it feels. Of course, we shouldn’t write <em>“OnUzytkownikSelected”</em>, but <em>“CzyUzytkownikJestAdminem”</em>. It can help us use the business language better and make it easier to convey and understand what we are doing. It would definitely be an interesting sociological/psychological experiment.</p> <p>I do not have an established opinion yet. I have mixed feelings. I am curious about your opinion on this subject. What do you think?</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Form a wall! And other concerns about security]]>https://event-driven.io/en/form_a_wall/https://event-driven.io/en/form_a_wall/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/b9df152ec1e1d44c2dfc4a79fdfeb878/d2429/2021-07-14-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACkklEQVQozwXBWU/acAAA8L8VM4YS5WzlKlhoKUehVKAUKVAop6AgogxUkCkuIs544WZipsMs7jLb67LswYc9+bBp3D7Jtm+z3w+gsDkTYDkvTeMEY/eqJlSTGn2U8i3zyVq6wRNojJ5F0XwkFO4fHHa3Op8+vD+/GAwu37gIB4jQM2KA9TkoxuFeETN+0gVrEYpKbDw5S0U2FXLCipUox5xWjXY6vf7x0f3d/c+73w8PvywGPXDZKBSGTRrEY8FqLF3morgFr+Vq85EEY2M87jrvWbMaoupxLaKdJAh8sbrM+Fif2zkx+hhYdbByTG7RajibuZmub9f6NrP1qFrZFBOleOqktV4XinoFPgzJpMMStWL8+vrzY6lSAoBsBAIcYbcicIKcynjI7tLrYrJFoua1uFDjhRhF5UNsMcTZ9VOoEdMolJhRt9/bxYy4cdKg1+lAkuWLQUqwm9xmjCFTmVg5F88XQzPZYJgyGzn71CzjTjN0d3t3daXdXFldqlQuL97d3v64unoLjovpGpf32kWzkTAhZJRNZeOzPBuz6M20BY078Xm/J0i6wxwPSRD5KAxJVAvlxuD84uLVAIhUiySPhVAvwQiqCQS3OGnntIf04ZjHYSXEaXoxErXChmw6d7B/2mg8q9U7AS9dKS3QlB/IkKde19qgUbhsNwMEicCmAM05cZeLoApupsdzLxYiST16+Pzk759/3758vbn5XimXUmJhTKYEhGMr7BA2Ulw9mcz6WbUCceDuglgPhvph53YMWyxyFb2SZcOF09Or9mprb2en3WymxAIERkCj+nGOr+a8eMzjCpHkuFxVigR3l14a8TOdraswrPu8ezAiAEgBgHrskUIGDY2AITA0KoWk/wFT1Z+hu3om4QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/b9df152ec1e1d44c2dfc4a79fdfeb878/a331c/2021-07-14-cover.png" srcset="/static/b9df152ec1e1d44c2dfc4a79fdfeb878/36ca5/2021-07-14-cover.png 200w, /static/b9df152ec1e1d44c2dfc4a79fdfeb878/a3397/2021-07-14-cover.png 400w, /static/b9df152ec1e1d44c2dfc4a79fdfeb878/a331c/2021-07-14-cover.png 800w, /static/b9df152ec1e1d44c2dfc4a79fdfeb878/d2429/2021-07-14-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>There were 0.3 seconds left till the end of the NBA match. Detroit Pistons were leading by a single point against San Antonio Spurs. The ball was in the hands of San Antonio players. As usually in basketball, the last seconds were interrupted by breaks for the coaches to set plays. Stan Van Gundy, Pistons’ coach, had only one recipe:</p> <p><strong><em>“We just form a fucking wall!!!”</em></strong></p> <p>Check it out with the rest here:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/YucvWt7CNn4?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>The Wall was also built in the north from Winterfell. Even bigger than what the Pistons had set. Additionally, a magical one that could only be crossed overhead. Which in itself was an almost impossible task.</p> <p>This wall had only one disadvantage. It was secured only on one side - the one from which attacks were expected. If someone walked over the Wall or attacked from the other side, then all was lost.</p> <p><strong>When I started my career, SQL Injection and Cross-Site Scripting were perceived as sophisticated attacks.</strong> Mature admins installed MSSQL servers with the Internet turned off, because by default, open to the world was a potential source of the attack. Setting up the firewall wasn’t even a standard.</p> <p>Today we have easier. By using the cloud, many things are warranted. Suppliers protect us from basic break-ins. For instance, they’re blocking DDoS attacks and many other attacks that we don’t even know existed. They put up a wall that is hard to breakthrough.</p> <p><strong>However, some time ago, I found that the wall built by cloud providers may be similar to the one from Game of Thrones.</strong> It’s not an issue with cloud providers. It’s an issue with us.</p> <p>Trust in cloud security and the focus on the technical aspect of security make us feel too comfortable. We have HTTPS, OAuth, VPCs, and that’s great. The issue is that too often, it makes us think that’s enough.</p> <p>I saw multiple times that security policies to resources like a database were defined without considerations. The application or module had all possible permissions. Instead of assigning only rights to required database schemas, files or operations, grants to all were given. Because of that, the website could get access to potentially everything. The typical answer when such a case was found: <em>“it’s just an internal configuration, the API is secured”</em>. That’s a classic example of ignorance and/or laziness.</p> <p>Another case, more drastic. Website authenticated with certificates. <strong>A pair of certificates shared by all clients.</strong> Request signed in the application code and certificates compiled into the application code. Answer: <em>“The client would have to decompile it. That’s likely not gonna happen.”</em> Yes, <em>that’s not gonna happen</em>, a.k.a. Famous last words. If someone knows what to look for, it will be about 5 minutes to decompile it and find it. What can a clever, bored customer’s developer do with a certificate compiled in your tool?</p> <p>The other case is the application keys security. They are often used for integration. Unlike passwords, they are not rotated. They usually have a long lifecycle, and someone who has access to them can invoke our services.</p> <p>Kevin Mitnick said <strong><em>“If an attacker wants to break into a system, the most effective approach is to try to exploit the weakest link - not operating systems, firewalls or encryption algorithms—but people”</em></strong>. It’s still valid nowadays. The most vulnerable point in the security of our systems is us. We enter passwords such as daughter’s name, qwerty etc. We put sticky notes with passwords on our monitors or save passwords in text files.</p> <p>When creating applications, we usually have to make quality compromises. <em>“Okay, in this situation, system wil crash, but we can live with that.”</em> Security is not a place where we can do that. The consequences are much more severe. By definition, we should block user’s actions and provide precisely what we allow.</p> <p>Hackers usually look for security holes. When they find one, they keep digging. They will break through one wall and look for a gap in the next one. What if we only have one wall?</p> <p>Then someone having a user password will get access to the endpoint and service logic. If we then allow the service to perform any action on the database, in theory, a person with access to the service can do anything with our data. Of course, we can say that <em>“only as much as the code allows”</em>. Well, but if the code has bugs and security holes? It doesn’t have to be our code. What if the libraries we use have zero-day vulnerabilities? For example, see what could be done by specifying a binary string in Java: <a href="https://github.com/frohoff/ysoserial">https://github.com/frohoff/ysoserial</a>. Then think about what you can do with the scripted Java, for example, <a href="https://medium.com/intrinsic/common-node-js-attack-vectors-the-dangers-of-malicious-modules-863ae949e7e8">https://medium.com/intrinsic/common-node-js-attack-vectors-the-dangers-of-malicious-modules-863ae949e7e8</a>.</p> <p><strong>Another issue: test users.</strong> Practically everyone has a <em>“qauser”</em> with the password <em>“qwerty12345”</em> or just <em>“test”</em>. Sometimes even a better password, but still one that everyone knows. As a rule, the password should not be changed because it is inconvenient. What if one of the employees is clever and has malicious intentions? Or he has been fired and is disappointed and wants to take revenge? What if it turns out that this user is also a cloud user (e.g. Azure AD) and other accesses within the system are directly assigned to him? What’s worse if the user is an admin? When configuring such users, we often do not realize how much can be done with such a user.</p> <p>Do you trust your coworkers? Great! What if we gave access to test users to our customers? Or we generated an access key that lasts a year? Will you also vouch for customer’s employees? Yes? Will you also guarantee that the customer is secured correctly and no one intercepted these credentials?</p> <p>Nowadays, advanced security methods are at your fingertips. We can even give every employee access to a password manager (e.g. <a href="https://bitwarden.com/">BitWarden</a>, <a href="https://1password.com">1Password</a>). We can set 2-factor authentication, we can rotate passwords. We can cut off access immediately. The same with databases, we can do password rotations using tools like <a href="https://www.vaultproject.io/">Vault</a>. Each cloud provider has a similar built-in tool for that. The complexity of configuring that, even for on-premise solutions, is not high. Everything is possible.</p> <p>However, what is the most difficult is thinking and being cautious about what we are doing. We have to be concerned about security. Some things can be done with a willingness to step up and following the basic rules.</p> <p><strong>Therefore, before we grant access to any resources before we say that <em>“nobody will try that”</em>, let’s think if this is really the wall we want to build.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. if you liked this article, then you may also find interesting <a href="/en/how_money_in_cloud_impacts_architectural_decisions/">“How money in Cloud impacts Architectural decisions?”</a></p><![CDATA[Let's take care of ourselves! Thoughts on compatibility]]>https://event-driven.io/en/lets_take_care_of_ourselves_thoughts_about_comptibility/https://event-driven.io/en/lets_take_care_of_ourselves_thoughts_about_comptibility/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/522acaba81dc977bff3d26308592b15c/d2429/2021-07-07-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AJGgnzmtxjevyDuxyj20zEG3z0O60Ua+1FPD11fE1VbQ42KXonJRVXFhZ2O+yWzT3orT3bff5bnh6IDP2ACjsrM/ts07t888udFCvdRFwdZLxdlPyNtRytxTzNxu1OF0ZGh0nKRxeH5p1eB61N2d2uGv3uWO1t+Ly9IAm8DCOrzTXcDQnNDaueDngdfiSM/dVdPfVtPeWt/ra6iwcmltbrrCbIiMcePsddXdftjhkdjhh9ffl8nQAIe1t0q90G+ruKS+w9Pb3r/f433V3mPa42na43Dk7XNxd3azum6rsnCnrHzl7oXa4pPQ18Xh5cXg47HO0gCHfo9e0NplxM9y0NiQ3eSt2eG6192M09qi3eSgur+EZ2iH6vN9lZx/xMuO5++eztWKo6e/1NaivsGcu78AnUlddnN7fb7CabC2X4iOkbG3pNvhpdPZtNnekGFgtaqrsuTqh4mNo9PZtt/jstTaja+yncnMYHR3Z3p+AHQtN3o9QHJYV1o6ODgUFEIkJXJ6fZawtI+RkYBDQbbk6aG9wpV8fsPd4Mvf4bTJy3mDg19ubUFLST1MSwCdREScQUN/NDZlNzZiOjpZMDBZLStfNTRmMC9bPTyCfoODh4lhVVOElpZ+ZmVnYmBFTEozNzcpMTAyMjAAnzk9kj9BjEFAgDY3dDY2bTQ0ZTw5VS8uZTQydDw4bVhYcmZjaElDamNjSz08PDEuSENBPj08SkdFS0lHAIowNJdBQowzOYozOHQoLVEdIpV7dnFJSFMoKWI1NGI2NnNMS3xJR512c558eoxzcn5wcIp6eYp+foR5eQCTOz6OOjyMOjyFODyGPUB8Ozx4Oj2MQ0ZxLjFhKixgKixjKy1jNzl7RkeIVVl9VVt4XGB7WmCGYGSCVlkIuVzHfkQqQwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/522acaba81dc977bff3d26308592b15c/a331c/2021-07-07-cover.png" srcset="/static/522acaba81dc977bff3d26308592b15c/36ca5/2021-07-07-cover.png 200w, /static/522acaba81dc977bff3d26308592b15c/a3397/2021-07-07-cover.png 400w, /static/522acaba81dc977bff3d26308592b15c/a331c/2021-07-07-cover.png 800w, /static/522acaba81dc977bff3d26308592b15c/d2429/2021-07-07-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In the last year, the word responsibility is used in all possible ways. Responsibility for us, for others. We can be heroes wearing masks instead of cloaks. Caring about others is essential not only in those crazy days but in general. We should take care of our colleagues. I often laugh that the best form of working in a corporation is doing nothing. Working in a corporation is a type of work where you are not rewarded for doing good, but you’ll be punished for doing wrong. So the less we do, the less chance that we’ll be wrong. It’s similar to the doctors’ <a href="https://en.wikipedia.org/wiki/Primum_non_nocere">“Primum non Nocere”</a> rule. Still, not quite the same.</p> <p>There is also a football saying <em>“it’s better to stand smartly than run around dumbly”</em>. Work smart, not hard. Sometimes doing less or slower produces better and faster results than doing something quickly. It doesn’t mean that we should avoid deeds, but we should think in advance about the effects of our actions.</p> <p>The basis for working in teams, especially remote ones, is respect for compatibility. We all heard about backward compatibility. But did we understand it?</p> <p>Backward compatibility literally means that our new changes will work properly with the application’s current (or previous) state. What does this mean in practice? Are migrations backwards compatible? Not really. They are transforming the earlier version of the application to the new one. Why is this not compatible? Imagine that we scale our system horizontally. We have several instances of the same service (to ensure better availability). We have the so-called rolling update, e.g. we deploy the new service’s version instance by instance. We want to maintain the highest possible availability. What will happen if the first deployed instance will upgrade the shared storage schema? All other instances may start failing until we upgrade them.</p> <p>Another situation is when we make a “core” library, which other libraries use, and decide to add the new parameter to the existing method. That may look like a harmless change, but what will happen if we make this parameter required (no default value) and someone updates to a new version of our package? Yup, compilation will fail.</p> <p>If we’re using an event-based architecture and we noticed there was a typo in our contract. I know that having <em>UserAnme</em> instead of <em>UserName</em> isn’t cool. We may decide to change it. If we do that and release a version and start publishing the <em>“corrected event</em> other systems subscribed to it will crash. They won’t deserialise and understand the new format without changing their code and making a new deployment. For us humans, readability is essential. Computers do not care about that, as long as they get what they’re expecting.</p> <p>Of course, automated tests can help with that. Still, they won’t resolve the real issue - thinking in advance and caring about the others work. Maybe you could say, <em>“Oh, someone from the other team should do update, it’s just five minutes of work.”</em>. Perhaps it’s five minutes of work, but it’s 5 minutes of work for you. Even if this someone is informed about it and does it, it is never that short. Add to that:</p> <ul> <li>context switching,</li> <li>writing the code and automated tests,</li> <li>integration checking if it really works,</li> <li>pull request,</li> <li>code review,</li> </ul> <p>Add to that time spent by several people reviewing the changes and potential discussions. And we are talking about an optimistic scenario, not a case where someone finds out when they get a production error because “something is wrong”. Is it really five minutes?</p> <p>Jeff Sutherland, in his book <a href="https://www.goodreads.com/book/show/19288230-scrum">Scrum: The Art of Doing Twice the Work in Half the Time</a>, cites research from Palm:</p> <p><em>“They looked at the “Matts” across the entire company—hundreds of developers—and they decided to analyse how long it took to fix a bug if they did it right away versus if they tried to fix it a few weeks later. Now, remember, software can be a pretty complicated and involved thing, so what do you think was the difference?</em></p> <p><strong><em>It took twenty-four times longer.</em></strong> <em>If a bug was addressed on the day it was created, it would take an hour to fix; three weeks later, it would take twenty-four hours. It didn’t even matter if the bug was big or small, complicated or simple—it always took twenty-four times longer three weeks later. As you can imagine, every software developer in the company was soon required to test and fix their code on the same day.”</em></p> <p>I suggest the following rule:</p> <p><strong>DO NOT MAKE BREAKING CHANGES AND DO NOT BREAK OTHER PEOPLE WORK. NEVER!</strong></p> <p>Sounds radical? Maybe, but it is true. There is always a way to perform changes in a non-breaking manner. We can always do a step by step process.</p> <p>If we’re creating an OSS library, we can mark features we want to remove with obsolete markers. We should add information about why and when we want to remove it and add a migration path. Then we can give our users time to migrate and not surprise them. In one of the follow-up releases, we can remove it.</p> <p>Literally, it will be a breaking change but done in a non-breaking manner. If you are a library maintainer and provide breaking changes in each release, I can guarantee that you won’t reach mainstream or broader usage. Most companies expect predictability. If you keep posting breaking changes now and then, users will either not move to newer versions or quit. Read more in my article <a href="/en/how_to_start_with_open_source/">“How to get started with Open Source?”</a>.</p> <p>Of course, sometimes breaking changes are needed to remove the ballast. Then migration guidelines are necessary to help your users. Still, it’s a tactical thing that you should avoid if you can.</p> <p>There are ways to reduce the number of breaking changes. For instance, keeping everything private, that doesn’t have to be public. If you expose something, then you have to assume that someone uses it. Follow the <a href="https://semver.org/lang/pl/">Semantic Versioning</a> rules. Also, grouping the breaking changes into a single release.</p> <p>It’s also worth providing migration tools (e.g. we did such a thing in EventStoreDB with <a href="https://replicator.eventstore.org">Replicator</a>). Nevertheless, those are tactics. I stand on my take that the strategy to be reliable and effective is not doing breaking changes.</p> <p><strong>To makes things harder, backward compatibility is not the only compatibility we should think of.</strong> There is also forward compatibility? It means that we have to anticipate that our code may be called by the newer version of the code/service. It is backward compatibility seen from the perspective of this old code/state. For example, if we listen to an event, e.g. <em>UserAdded</em>, which has the fields <em>Id</em>, <em>Username</em>, we can expect that, for example, in the future, we may get its newer version with new fields, e.g. <em>Email</em>, <em>BirthDate</em>, etc.</p> <p>Of course, we can’t predict how our application will evolve. We will not handle fields that we do not know. We don’t have to predict everything. Nevertheless, we have to keep in mind that the contract may change, and if it is backwards compatible, we should handle it. How can we do that? Well, we can, for example, not throw an error when we get additional fields in the payload. If we do Event Sourcing and save the events to the database as JSON, we should keep them as they are. Thanks to that, when we update the code, we will create the new logic and handle the extended data.</p> <p>We should help ourselves and help others. Let’s try not to break others’ work because it really pays off, literally. It really isn’t a waste of time.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[How to get the current entity state from events?]]>https://event-driven.io/en/how_to_get_the_current_entity_state_in_event_sourcing/https://event-driven.io/en/how_to_get_the_current_entity_state_in_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3dde55ec58dd58211a77b719741770bf/d2429/2021-06-30-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAAA5klEQVQoz2P4jw38+/fv39+//wkBBjxyP798/Pz66dd3L/79+0ecZrC67+9f3dm+6Ozi7p1Tqre1Jr5/cgcs85eAZohrH18+vq4+YW1jysS8oAWFfu8eXidOM1jF69sX9/XnbWlJXlwZsb4p7tPz+5CAIMrPf379PLd8wt7evLm53hvbMv7++QUxmSg//3j/6ua8iutzqxYXBmxqiPn+6iFxNoNV/Pr66eS00mN9WZurQ/c1x/549xwuRVRUvbx05MSE3JMzK86t7P/5/StxzkYCr6+duH1g3YcXj3AlGJyacSUMZM0A4cho6ITtNOYAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/3dde55ec58dd58211a77b719741770bf/a331c/2021-06-30-cover.png" srcset="/static/3dde55ec58dd58211a77b719741770bf/36ca5/2021-06-30-cover.png 200w, /static/3dde55ec58dd58211a77b719741770bf/a3397/2021-06-30-cover.png 400w, /static/3dde55ec58dd58211a77b719741770bf/a331c/2021-06-30-cover.png 800w, /static/3dde55ec58dd58211a77b719741770bf/d2429/2021-06-30-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Today I’d like to go back a bit to the basics of Event Sourcing. I recently realised that I often cover more advanced topics. So sometimes, it’s handy to take a step back and polish the basics.</p> <p>In Event Sourcing, the application state is stored in events. They are the results of the business operations. When we add an event, it is placed at the end of an immutable structure called append-only log (read more in my post <a href="/en/relational_databases_are_event_stores/">“What if I told you that Relational Databases are in fact Event Stores?”</a>). Events are the source of truth. Hence the name. This has many advantages, such as:</p> <ul> <li>history of changes in our system,</li> <li>easier diagnostics,</li> <li>closeness to business, as our code structures correspond to business facts.</li> </ul> <p>Many people believe that Snapshots are the must-have in the Event-Sourced system. Instead of retrieving all stream events to rebuild the state, we could retrieve one record and use it for our business logic. It sounds promising and can be useful as the technical optimisation technique but should not be used as a ground basis. Isn’t loading more than one event a performance issue? Frankly, it’s not. Downloading even a dozen, or several dozens of small events is not a significant overhead. Events are concise, containing only the information needed. Event Stores are optimised for such operations, and the reads scale well (read more in my article <a href="https://www.eventstore.com/blog/snapshots-in-event-sourcing">“Snapshots in Event Sourcing”</a>).</p> <p>Using Event Sourcing does not have to cause an automatic revolution in our code. We can still use aggregates/entities. In Event Sourcing, events are logically grouped into streams. Streams are ordered sequences of events. One stream includes all events for a given business object, e.g. <em>InvoiceInitiated</em>, <em>InvoiceIssued</em>, <em>InvoiceSent</em>.</p> <p>Thus recommended approach is to build the current state from events. To do so, we need to perform the following steps:</p> <ol> <li>Get all events for a given stream. We choose them based on the stream identifier (derived from the business object/record id). An event store retains the events for a given stream in the order they were appended; retrieval should preserve the order.</li> <li>Create a default, empty entity (e.g. using the default constructor).</li> <li>Apply each event sequentially to the entity.</li> </ol> <p>The first two points are obvious, but what does it mean to <em>apply an event</em>? There are two ways:</p> <ul> <li>Use the <em>When</em> function. We’re passing a generic event object as an input parameter. Inside the method, we can use <em>“pattern matching”</em> to determine what logic applies to the specific event type. It is a framework-independent solution. You have to write a bit more yourself, but there is less magic.</li> <li>Some frameworks provide convention-based solutions that simplify handling and make it a bit more magical. For example, in <a href="https://martendb.io/">Marten</a>, the aggregate class should have an <em>Apply</em> method for every event it can handle. This is because the built-in <a href="https://martendb.io/documentation/events/projections/">AggregateStream</a> method is reading events and applying them internally.</li> </ul> <p>The process of rebuilding the state from events is also called <em>Stream Aggregation</em>.</p> <p>Let’s focus for now on the general approach to understand the flow properly. In C#, it might look like that:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">Person</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Address <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">InvoiceInitiated</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">double</span></span> Amount<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> Number<span class="token punctuation">,</span> <span class="token class-name">Person</span> IssuedTo<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> InitiatedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">InvoiceIssued</span><span class="token punctuation">(</span> <span class="token class-name"><span class="token keyword">string</span></span> IssuedBy<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> IssuedAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">InvoiceSendMethod</span> <span class="token punctuation">{</span> Email<span class="token punctuation">,</span> Post <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">InvoiceSent</span><span class="token punctuation">(</span> <span class="token class-name">InvoiceSendMethod</span> SentVia<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> SentAt <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">InvoiceStatus</span> <span class="token punctuation">{</span> Initiated <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> Issued <span class="token operator">=</span> <span class="token number">2</span><span class="token punctuation">,</span> Sent <span class="token operator">=</span> <span class="token number">3</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">Invoice</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span><span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">double</span></span> Amount <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Number <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">InvoiceStatus</span> Status <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">Person</span> IssuedTo <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> InitiatedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> IssuedBy <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> IssuedAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">InvoiceSendMethod</span> SentVia <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> SentAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">When</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token class-name">InvoiceInitiated</span> invoiceInitiated<span class="token punctuation">:</span> <span class="token function">Apply</span><span class="token punctuation">(</span>invoiceInitiated<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">InvoiceIssued</span> invoiceIssued<span class="token punctuation">:</span> <span class="token function">Apply</span><span class="token punctuation">(</span>invoiceIssued<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token class-name">InvoiceSent</span> invoiceSent<span class="token punctuation">:</span> <span class="token function">Apply</span><span class="token punctuation">(</span>invoiceSent<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">InvoiceInitiated</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Number<span class="token punctuation">;</span> Amount <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Amount<span class="token punctuation">;</span> Number <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>Number<span class="token punctuation">;</span> IssuedTo <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>IssuedTo<span class="token punctuation">;</span> InitiatedAt <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>InitiatedAt<span class="token punctuation">;</span> Status <span class="token operator">=</span> InvoiceStatus<span class="token punctuation">.</span>Initiated<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">InvoiceIssued</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> IssuedBy <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>IssuedBy<span class="token punctuation">;</span> IssuedAt <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>IssuedAt<span class="token punctuation">;</span> Status <span class="token operator">=</span> InvoiceStatus<span class="token punctuation">.</span>Issued<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Apply</span><span class="token punctuation">(</span><span class="token class-name">InvoiceSent</span> @<span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> SentVia <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>SentVia<span class="token punctuation">;</span> SentAt <span class="token operator">=</span> @<span class="token keyword">event</span><span class="token punctuation">.</span>SentAt<span class="token punctuation">;</span> Status <span class="token operator">=</span> InvoiceStatus<span class="token punctuation">.</span>Sent<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>The usage as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name"><span class="token keyword">var</span></span> invoiceInitiated <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvoiceInitiated</span><span class="token punctuation">(</span> <span class="token number">34.12</span><span class="token punctuation">,</span> <span class="token string">"INV/2021/11/01"</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Person</span><span class="token punctuation">(</span><span class="token string">"Oscar the Grouch"</span><span class="token punctuation">,</span> <span class="token string">"123 Sesame Street"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> invoiceIssued <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvoiceIssued</span><span class="token punctuation">(</span> <span class="token string">"Cookie Monster"</span><span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> invoiceSent <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InvoiceSent</span><span class="token punctuation">(</span> InvoiceSendMethod<span class="token punctuation">.</span>Email<span class="token punctuation">,</span> DateTime<span class="token punctuation">.</span>UtcNow <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 1,2. Get all events and sort them in the order of appearance</span> <span class="token class-name"><span class="token keyword">var</span></span> events <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> <span class="token punctuation">{</span>invoiceInitiated<span class="token punctuation">,</span> invoiceIssued<span class="token punctuation">,</span> invoiceSent<span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token comment">// 3. Construct empty Invoice object</span> <span class="token class-name"><span class="token keyword">var</span></span> invoice <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Invoice</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// 4. Apply each event on the entity.</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> events<span class="token punctuation">)</span> <span class="token punctuation">{</span> invoice<span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>If you prefer, you can add the base class with an abstract <em>When</em> method to write the more generalised logic.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token keyword">class</span> <span class="token class-name">Aggregate<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">T</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">protected</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">abstract</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">When</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> @<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Having that, we could write such a method for <a href="https://developers.eventstore.com/">EventStoreDB</a> to retrieve the aggregate state from events:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>TAggregate<span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Find</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TAggregate<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellationToken<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TAggregate</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> readResult <span class="token operator">=</span> eventStore<span class="token punctuation">.</span><span class="token function">ReadStreamAsync</span><span class="token punctuation">(</span> Direction<span class="token punctuation">.</span>Forwards<span class="token punctuation">,</span> <span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp"><span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">T</span><span class="token punctuation">)</span><span class="token punctuation">.</span>Name</span><span class="token punctuation">}</span></span><span class="token string">-</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">id</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">,</span> StreamPosition<span class="token punctuation">.</span>Start<span class="token punctuation">,</span> <span class="token named-parameter punctuation">cancellationToken</span><span class="token punctuation">:</span> cancellationToken <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> aggregate <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TAggregate</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token keyword">foreach</span> <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">var</span></span> @<span class="token keyword">event</span> <span class="token keyword">in</span> readResult<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> eventData <span class="token operator">=</span> <span class="token function">Deserialize</span><span class="token punctuation">(</span>@<span class="token keyword">event</span><span class="token punctuation">)</span><span class="token punctuation">;</span> aggregate<span class="token punctuation">.</span><span class="token function">When</span><span class="token punctuation">(</span>eventData<span class="token operator">!</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> aggregate<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>In <a href="https://martendb.io/">Marten</a> this will look as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>TAggregate<span class="token punctuation">?</span><span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Find</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TAggregate<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token class-name">Guid</span> id<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> cancellationToken<span class="token punctuation">)</span> <span class="token keyword">where</span> <span class="token class-name">TAggregate</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Aggregate</span><span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token punctuation">(</span><span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> _session<span class="token punctuation">.</span>Events<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">AggregateStreamAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TAggregate<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>end<span class="token punctuation">.</span>TripId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Of course, this is a highly imperative approach. However, if we prefer a functional approach, we could use a pattern I described in my article <a href="/en/partial_typescript/">Why Partial<Type> is an extremely useful TypeScript feature?</a>.</p> <p>In functional programming, we don’t need base classes. We don’t need aggregates. Instead, we’re splitting the behaviour (functions) from the state (entity).</p> <p>In TypeScript, having event and entity defined as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">type</span> <span class="token class-name">Event<span class="token operator">&lt;</span> EventType <span class="token keyword">extends</span> <span class="token builtin">string</span> <span class="token operator">=</span> <span class="token builtin">string</span><span class="token punctuation">,</span> EventData <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span><span class="token punctuation">,</span> EventMetadata <span class="token keyword">extends</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">=</span> Record<span class="token operator">&lt;</span><span class="token builtin">string</span><span class="token punctuation">,</span> <span class="token builtin">unknown</span><span class="token operator">></span> <span class="token operator">></span></span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> type<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>EventType<span class="token operator">></span><span class="token punctuation">;</span> data<span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>EventData<span class="token operator">></span><span class="token punctuation">;</span> metadata<span class="token operator">?</span><span class="token operator">:</span> Readonly<span class="token operator">&lt;</span>EventMetadata<span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Person</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> address<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span> <span class="token keyword">type</span> <span class="token class-name">InvoiceInitiated</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'invoice-initiated'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token builtin">number</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> amount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> issuedTo<span class="token operator">:</span> Person<span class="token punctuation">;</span> initiatedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">InvoiceIssued</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'invoice-issued'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token builtin">number</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> issuedBy<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> issuedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">InvoiceSent</span> <span class="token operator">=</span> Event<span class="token operator">&lt;</span> <span class="token string">'invoice-sent'</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> <span class="token builtin">number</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> sentVia<span class="token operator">:</span> InvoiceSendMethod<span class="token punctuation">;</span> sentAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">InvoiceEvent</span> <span class="token operator">=</span> <span class="token operator">|</span> InvoiceInitiated <span class="token operator">|</span> InvoiceIssued <span class="token operator">|</span> InvoiceSent<span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">Invoice</span> <span class="token operator">=</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> <span class="token builtin">number</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> amount<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">;</span> status<span class="token operator">:</span> InvoiceStatus<span class="token punctuation">;</span> issuedTo<span class="token operator">:</span> Person<span class="token punctuation">;</span> initiatedAt<span class="token operator">:</span> Date<span class="token punctuation">;</span> issued<span class="token operator">?</span><span class="token operator">:</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> by<span class="token operator">?</span><span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> at<span class="token operator">?</span><span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> sent<span class="token operator">?</span><span class="token operator">:</span> Readonly<span class="token operator">&lt;</span><span class="token punctuation">{</span> via<span class="token operator">?</span><span class="token operator">:</span> InvoiceSendMethod<span class="token punctuation">;</span> at<span class="token operator">?</span><span class="token operator">:</span> Date<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token operator">></span><span class="token punctuation">;</span></code></pre></div> <p>We can define the <em>When</em> method as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">when</span><span class="token punctuation">(</span> currentState<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>CashRegister<span class="token operator">></span><span class="token punctuation">,</span> event<span class="token operator">:</span> CashRegisterEvent <span class="token punctuation">)</span><span class="token operator">:</span> Partial<span class="token operator">&lt;</span>CashRegister<span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'invoice-initiated'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token builtin">number</span><span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span><span class="token builtin">number</span><span class="token punctuation">,</span> amount<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>amount<span class="token punctuation">,</span> status<span class="token operator">:</span> InvoiceStatus<span class="token punctuation">.</span><span class="token constant">INITIATED</span><span class="token punctuation">,</span> issuedTo<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>issuedTo<span class="token punctuation">,</span> initiatedAt<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>initiatedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'invoice-issued'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> status<span class="token operator">:</span> InvoiceStatus<span class="token punctuation">.</span><span class="token constant">ISSUED</span><span class="token punctuation">,</span> issued<span class="token operator">:</span> <span class="token punctuation">{</span> by<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>issuedBy<span class="token punctuation">,</span> at<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>issuedAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token string">'invoice-sent'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> status<span class="token operator">:</span> InvoiceStatus<span class="token punctuation">.</span><span class="token constant">SENT</span><span class="token punctuation">,</span> sent<span class="token operator">:</span> <span class="token punctuation">{</span> via<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>sentVia<span class="token punctuation">,</span> at<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>sentAt<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Using the <em>reduce</em> method and <em>Partial type</em> described in the previous article, we can define the generic stream aggregation method as:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token generic-function"><span class="token function">aggregateStream</span><span class="token generic class-name"><span class="token operator">&lt;</span>Aggregate<span class="token punctuation">,</span> StreamEvents <span class="token keyword">extends</span> Event<span class="token operator">></span></span></span><span class="token punctuation">(</span> events<span class="token operator">:</span> StreamEvents<span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token function-variable function">when</span><span class="token operator">:</span> <span class="token punctuation">(</span> currentState<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>Aggregate<span class="token operator">></span><span class="token punctuation">,</span> event<span class="token operator">:</span> StreamEvents <span class="token punctuation">)</span> <span class="token operator">=></span> Partial<span class="token operator">&lt;</span>Aggregate<span class="token operator">></span><span class="token punctuation">,</span> check<span class="token operator">?</span><span class="token operator">:</span> <span class="token punctuation">(</span>state<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>Aggregate<span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> state <span class="token keyword">is</span> Aggregate <span class="token punctuation">)</span><span class="token operator">:</span> Aggregate <span class="token punctuation">{</span> <span class="token keyword">const</span> state <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">reduce</span><span class="token generic class-name"><span class="token operator">&lt;</span>Partial<span class="token operator">&lt;</span>Aggregate<span class="token operator">>></span></span></span><span class="token punctuation">(</span>when<span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>check<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">'No type check method was provided in the aggregate method'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token operator">&lt;</span>Aggregate<span class="token operator">></span>state<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">check</span><span class="token punctuation">(</span>state<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token string">'Aggregate state is not valid'</span><span class="token punctuation">;</span> <span class="token keyword">return</span> state<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Then we could use it as follows to read events and rebuild the current state:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> events<span class="token operator">:</span> InvoiceEvent<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> resolvedEvent <span class="token keyword">of</span> eventStore<span class="token punctuation">.</span><span class="token function">readStream</span><span class="token punctuation">(</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">invoice-</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>invoiceId<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span> <span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> events<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>InvoiceEvent<span class="token operator">></span><span class="token punctuation">{</span> type<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">!</span><span class="token punctuation">.</span>type<span class="token punctuation">,</span> data<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">!</span><span class="token punctuation">.</span>data<span class="token punctuation">,</span> metadata<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">?.</span>metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> invoice <span class="token operator">=</span> <span class="token generic-function"><span class="token function">aggregateStream</span><span class="token generic class-name"><span class="token operator">&lt;</span>Invoice<span class="token punctuation">,</span> InvoiceEvent<span class="token operator">></span></span></span><span class="token punctuation">(</span> events<span class="token punctuation">,</span> when<span class="token punctuation">,</span> isInvoice <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Both approaches have pros and cons. Object-oriented way brings more ceremony. However, it has an advantage against the functional approach, keeping object state and behaviour grouped together.</p> <p>Stream Aggregation is a simple but powerful pattern. It allows easy debugging, writing unit tests and better control over what is happening. It’s also the basis for doing the essence of Event Sourcing, so treating events as the source of truth.</p> <p>Check detailed samples in my repositories:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore">https://github.com/oskardudycz/EventSourcing.NetCore</a></li> <li><a href="https://github.com/oskardudycz/EventSourcing.NodeJS">https://github.com/oskardudycz/EventSourcing.NodeJS</a></li> </ul> <p>For Java version check the <a href="/en/how_to_write_left_fold_collector_in_java">follow-up article showing how to do make it generic using a custom streams collector</a></p> <p>Cheers!</p> <p>Oskar</p><![CDATA[When not to use Event Sourcing?]]>https://event-driven.io/en/when_not_to_use_event_sourcing/https://event-driven.io/en/when_not_to_use_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/07df6a29039476333b631db90d492320/d2429/2021-06-23-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABa0lEQVQoz2NgwA0YGRkYGUgETExM/CJSzKxs3Pzijl4xnJycRGljZATZJCWn3DFtpbyWra+PXVFRlqauBdhEZogsAc2iUvJxufWmJqqHFuseXOqiqWPIwMDAzMwMcRQBzUIScnp2AavaWNZNkDq6ysfTWZOBgYGTg0NQUBCuBptmsMEqOuZpKSE3FzOcWWNyaVfx7s2LPTz9+vr6zp0+6evnB3YFNvshbisrrXx15+ybuzse37+0+8Dx1Vu2vnz15uPn77fu3Dtz7pKcvALYfiYs4czAwBCSHNezZGF+SXFStLe/n755mOf123cmT5u5dOX6/YdOWFha4/Q8MyOTcJgHq6u2rql4oqesh7NcYErAsZMXbVwDXP1CSyrqRUXFsPgcwheUkzHJSxOwVDFyVI9wV/ZxVFTUFJwxqbC9qTQmMd3TNwhfaIlYmfAFOfEqCmuaynnayAQ6KShoiBkZ83lYy1kZyoEcjGQnAI6sYvuaQTm1AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/07df6a29039476333b631db90d492320/a331c/2021-06-23-cover.png" srcset="/static/07df6a29039476333b631db90d492320/36ca5/2021-06-23-cover.png 200w, /static/07df6a29039476333b631db90d492320/a3397/2021-06-23-cover.png 400w, /static/07df6a29039476333b631db90d492320/a331c/2021-06-23-cover.png 800w, /static/07df6a29039476333b631db90d492320/d2429/2021-06-23-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Event Sourcing is perceived as a complex pattern that’s challenging to learn. Typically it’s matched with the financial industry or big enterprise systems. If you’re familiar with my posts, you already know that I disagree with this categorisation. <strong>I think that Event Sourcing is also relevant for the smaller systems.</strong></p> <p>Indeed, Event Sourcing shines the most when we can work with the business to find the business events. <a href="https://www.eventstorming.com/">Event Storming</a> proved that events work great as a way to describe business processes. We can use them as <em>“checkpoints”</em> of our workflow.</p> <p>Events are also essential as a data model. If we’re storing them in a durable event store (e.g. <a href="https://www.eventstore.com">EventStoreDB</a> or <a href="https://martendb.io/">Marten</a>) then we won’t lose any business data. The nature of Event Sourcing is storing the results of each business operation. We can use this information later for integration between services, advanced reporting etc.</p> <h2 id="event-sourcing-is-not-a-silver-bullet" style="position:relative;"><a href="#event-sourcing-is-not-a-silver-bullet" aria-label="event sourcing is not a silver bullet permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Event Sourcing is not a silver bullet.</h2> <p>As with a hammer, you can drive a nail, but you can also hit yourself on the finger. There are cases when using it can be state-of-the-art. <strong>The most important thing to understand is that Event Sourcing is not a system-wide architecture concept. It should be considered at the module level.</strong> It’s a perfectly valid case if part of the system is Event-Sourced. It’s not an all-or-nothing decision. For example, we can use Event Sourcing in the core business module, and for “supportive modules”, we can use a traditional approach.</p> <p>What do I mean by <em>“supportive modules”</em>? I mean, for instance, a CMS (<em>Content Management System</em>), e.g., Confluence, WordPress, OneNote or even Excel. Such systems can be treated as bags for data. You put some data in there, sometimes in plain text, sometimes a table, sometimes a photo. We do not intend to perform advanced data analysis: we just want to store and retrieve data. It’s not essential to know the type of data we’re storing. All will be aligned and handled with the same patterns, e.g. a grid with data, edit form. We create, update, read or delete records. We can use such systems both for everything from wedding planning to warehouse inventory and budgeting.</p> <p><strong>In this definition, we could say <em>“in my system the Event Sourcing won’t work out because it is a simple CRUD”</em>.</strong> Even if we add more fancy technical features like permissions management or logging, it’s still a CRUD just wrapped in layers like a Matrioshka. We create some tables, add some services and add some forms, as the finishing touch on top of it.</p> <p>I agree that Event Sourcing will not suit such systems. However, it usually happens that at some point, business comes to us and says:</p> <ul> <li><em>“You know what, I’d like to have a template for monthly budgeting.”</em></li> <li><em>“This template is great. Could we check when people are using it?”</em></li> <li><em>“Ah, the HR department would like to have a section that’s only applicable for them with staffing information.”</em></li> <li><em>“HR department loves that! It’s great, but could we make some fields only editable for the manager?”</em></li> <li><em>“Can we send the budget automatically to the financial department?”</em></li> <li><em>“Integration works great! Can we add an approval process for the financial directors? If they approve the budget then it cannot be changed anymore, and if they reject it the budget should be sent back?”</em></li> </ul> <p><strong>If we ask a few more questions, then it appears that from the simple budgeting form, we get a complex process with different workflow paths.</strong> If we think about our system as CRUD, we should also ensure that we consider the growth expectation. Especially for greenfield projects, we may already know that even though the first phase will be simple, the critical decision about go/no-go will happen if the expected growth is reached. Therefore, we should ask a lot of <em>“whys”</em> and be proactive in investigating our business domain (read more in <a href="/en/bring_me_problems_not_solutions/">“Bring me problems, not solutions!”</a>).</p> <p>On the other hand, we should be careful and remember the basics. For example, I was working on a project that had a dedicated service for configuration. Even though it mainly contained classical dictionaries, all integration was run through events. The amount of repetitive, boilerplate code was enormous. For such cases, maybe an old-school but a battle-tested solution like replication would be enough.</p> <h2 id="technical-considerations-are-not-the-only-ones-we-should-evaluate" style="position:relative;"><a href="#technical-considerations-are-not-the-only-ones-we-should-evaluate" aria-label="technical considerations are not the only ones we should evaluate permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Technical considerations are not the only ones we should evaluate.</h2> <p>Socio-technical issues can be even more critical. I see three main categories here:</p> <ol> <li>The team is doing well with the current approach.</li> <li>The team could benefit from Event Sourcing but thinks they don’t need it.</li> <li>The team thinks that it needs Event Sourcing, but the team doesn’t have the competence and experience.</li> </ol> <p><strong>The first category is quite apparent. If you have a good process that works, then why fix something that’s not broken?</strong> If you do classical layered architecture and it works for you and your clients, then it might not be worth innovating just for the sake of doing it. I’m always saying that a well-done CRUD is much better than a poorly done Event Sourcing. There is nothing wrong with using unsexy technologies or patterns if that works. Of course, it’s worth considering if adding portions of Event Sourcing won’t make our life easier or open new possibilities. Especially trying it for the modules with the audit needs or complex workflows can benefit. Event Sourcing can also help in diagnostics and debugging. Nevertheless, even if something looks stupid but works then, it’s not stupid.</p> <p><strong>There is a joke:</strong></p> <p><em>The guy goes for a walk in the park. The weather is good, conditions are pleasant, but suddenly he hears groaning. He looks left, looks right and sees a pitiful dog lying near the bench. Worried, he goes to check what’s wrong.</em></p> <p><em>On the bench, the dog’s owner is sitting.</em></p> <p><em>The guy asks him: Why is this dog groaning? Is the dog hurt?</em></p> <p><em>Owner answers: He’s lying on the needle.</em></p> <p><em>The guy inquiries: Why the dog doesn’t get up?</em></p> <p><em>Because it doesn’t hurt that much.</em></p> <h2 id="thats-the-second-category-of-socio-technical-issues-reluctance" style="position:relative;"><a href="#thats-the-second-category-of-socio-technical-issues-reluctance" aria-label="thats the second category of socio technical issues reluctance permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>That’s the second category of socio-technical issues: reluctance.</h2> <p>The team has some problems with the current process. For example, the team members complain about it during private chats in the kitchen, but they’re not motivated enough to pull the trigger. Maybe they’d like to change something, as some things are not going smoothly, but they prefer to not start a revolution.</p> <p>Sometimes the reluctance does not come from the team, but something external, e.g. from the company culture. For instance, it might be the management that does not reward for good and punish for wrong. People might be afraid to try a new approach to not be blamed and punished. Plus, some people don’t want to take responsibility. They just want to “do their work”.</p> <p>If we’re aiming to drive the change, and we get backslash, then it’s easy to say that “those dinosaurs do not want that”. We may come to the conclusion that we’re hitting the second category described above. We should verify if we’re not interpreting the first category as the second. Maybe the current approach actually works, and only we are trying to change it? We should look inside ourselves and check if our motivations are pure. Don’t we want to do CV Driven Development or Hype-Oriented Programming?</p> <p>If it appears that Event Sourcing should help, then we should consider whether we have sufficient motivation for evangelisation. If so, then let’s try to do it in small bits. We might not even ask for permission. For example, we can try to rewrite a minor existing feature using Event Sourcing. Nothing too big, we’ll know the business case, and we won’t waste a lot of time if we fail. If we succeed, we could show by example the real benefit for our project. However, we should understand that this may not work out. A rational refusal shouldn’t clip our wings and cause burnout.</p> <p><strong>If we have the driving force (e.g. we are a technical leader, architect), let’s make sure we really need Event Sourcing before we start to use it.</strong> If we want to force our team or the entire organisation, make sure we want to take full responsibility. Someone has to take it, but let’s ask ourselves if we are aware of the consequences. Technologies and patterns also need to be matched to the capabilities of the team we have.</p> <h2 id="thats-how-we-came-to-the-third-socio-technical-category-over-enthusiasm" style="position:relative;"><a href="#thats-how-we-came-to-the-third-socio-technical-category-over-enthusiasm" aria-label="thats how we came to the third socio technical category over enthusiasm permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>That’s how we came to the third socio-technical category: over-enthusiasm</h2> <p>I personally played the Brutus role once. I killed the idea of using Event Sourcing in one of my projects. I did that even though the domain seemed to be a good fit. However, I was the only person experienced in Event Sourcing. We had a few teams distributed across three countries and two continents. I wouldn’t be able to onboard all of them and do proper knowledge sharing.</p> <p>Some developers, when learning that they’ll start a greenfield project, hear ka-ching. <em>“Now we’re gonna go crazy.”</em> It is a very tempting prospect, but is it really right? We need to remember that we’re building our systems to facilitate business processes. They should do it well, and the execution should be as cheap as possible for our client. If we start to play with technologies or begin our work writing the core libraries and architecture before we start delivering business cases, we’re making a grave mistake.</p> <p>We usually make the most important decisions when we are the dumbest: at the beginning of a project. We know the least about the business domain and technologies we use. It’s easy to fall into accidental complexity. It would be good if we at least learned from our mistakes. We could do that, but then next project, again a new set of technologies…</p> <p><strong>What I propose is to follow the principle of <em>“Start Small - Grow Big”</em>.</strong> Start with non-important business functionality. Then we won’t get into trouble if we fail. Money won’t be lost; no one will die. This functionality must be simple and match the Event Sourcing typical scenarios, e.g. feature with the auditing or diagnostics needs. Let’s try to bring it the most straightforward way, implement it, see what breaks down, what mistakes we made, and then, learning from this experience, try new things.</p> <p>Does that apply only to Event Sourcing? No, basically to every new pattern and technology unknown to us. There is nothing magical about it, but we often forget about such pragmatism.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. if you liked this article, you may also like the <a href="/en/sociological_aspects_of_microservices/">“Sociological aspects of Microservices”</a>.</p><![CDATA[Generic does not mean Simple]]>https://event-driven.io/en/generic_does_not_mean_simple/https://event-driven.io/en/generic_does_not_mean_simple/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/edf98ad5ea70869a387662bc22e479b7/d2429/2021-06-16-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACNElEQVQoz2N4/eq5h50xGyODgZygtZo4NwtDW0Pl//////77958QYKgpL2JlYNAW4Tg5q/zD0bnlHpoKoly3bl7/////ezB49erV8+fPv3z5AjIRBv6BjWYICwuW5WJo89F8tmPSvysr91V6iLEyrF696u/fv9OnT1+9evW8efMWLFjw6tWr/////0N1DkNOQnScsejOprCDsysvLiqflemkK8a6ffu2////b968+dSpU0eOHNm9e/ft27dfvXr14MGD27dvP3v27OvXryDNe+b0T4kymJ3j0RZhMSXNLdRAIifI8uPHj//+/Vu0aOGaNWv279+/d+/e+fPnb9iwYf/+/bNnz16zZs3Hjx9Bmh/furyqwO3VpqY78wq3VQRtzbftzQv5+fsPmiP/YQs/hu8/f+/syb0xK3Vfc8L0KNvOII35fY3//////PnT5s2bT548eejQoYMHD168eHH//v0nT57cunXrqlWrjh8/DtL8////7bNbF6YZHWkKnRtrkeKo+vjBnf///394/37Pnj0bNmw4dOjQiRMn1q5du3HjxufPn3/8+PH9+/dQZ2/etGZWiUeZjXi2o5K3toirAf+cmRMhsfLkyZPbt2+/fPny8+fP9+7du3///t+/f1GcLSHAfGRl9Nl5GYX2SnOaAmfWmwtws4YF+61fu+rL58+YPocDkGYnC8Vv9/v2rSkLdNL78nDerHZvDlYmHg4mTlZGI32NCf3dnz6BXPjnzx80a4GaARqu3Gcni4r8AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/edf98ad5ea70869a387662bc22e479b7/a331c/2021-06-16-cover.png" srcset="/static/edf98ad5ea70869a387662bc22e479b7/36ca5/2021-06-16-cover.png 200w, /static/edf98ad5ea70869a387662bc22e479b7/a3397/2021-06-16-cover.png 400w, /static/edf98ad5ea70869a387662bc22e479b7/a331c/2021-06-16-cover.png 800w, /static/edf98ad5ea70869a387662bc22e479b7/d2429/2021-06-16-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><a href="/en/cqrs_facts_and_myths_explained/">As you know, I am a fan and practitioner of CQRS.</a>. I believe that it is falsely considered as complicated. In my opinion, it can help even with the classic CRUD approach. For example, if we know that a given method is only used to return data, we can optimise it. Even in the <a href="https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping">ORM</a> (e.g. Entity Framework), knowing that we won’t modify the data, by disabling changes tracking, we can make queries run faster.</p> <p>Minor optimisations are fun, but these strategic changes are worth more. I often refer to <em>“Clean Architecture”</em> as <em>“Onion Architecture”</em>. Not only because of the layers’ abundance. Also, because of the specific smell that hangs around it. In <em>Onion Architecture</em>, we divide our code into horizontal layers: API layer, application layer, business layer, data layer, and so on and so forth. Matrioshka called a “decent, enterprise” architecture. Why am I clinging to the scent?</p> <p>The following things do not smell great to me:</p> <ul> <li>if we change something in a given layer, it will most likely affect all our business features.</li> <li>the barrier to entry to understand such architecture is overwhelming.</li> <li><em>Onion Architecture</em> is like a box of chocolates. You never know what’s inside. It’s hard to guess relationships between layers and components. If we wrap everything up in interfaces, finding what may happen on the API call is challenging.</li> <li>When we add or change functionality, we must remember the whole procedure and follow the checklists to not forget any detail.</li> </ul> <p>All of that conveys into <a href="/en/sociological_aspects_of_microservices/">cognitive load</a> and directly to software development and implementation costs. Moreover, it increases the risk of change. If we are going to change the generic repository, we risk breaking everything by having a bad if. This often leads to abandonment and avoidance of changes, or worse, workarounds with hacks.</p> <p>By cutting our architecture vertically, we can write more fine-tuned code for a given situation. Of course, I’m not talking about the Copypaste method here, but about writing simple code. Which is not simple at all and requires effort.</p> <p>While working on my presentation at <a href="https://4developers.org.pl/lecture_online_2021/#id=65099">the last 4Developers conference</a>, I prepared the following code:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Route</span> <span class="token punctuation">{</span> <span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token return-type class-name">IEndpointRouteBuilder</span> <span class="token function">UseRegisterProductEndpoint</span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">IEndpointRouteBuilder</span> endpoints<span class="token punctuation">)</span> <span class="token punctuation">{</span> endpoints<span class="token punctuation">.</span><span class="token function">MapPost</span><span class="token punctuation">(</span><span class="token string">"api/products/"</span><span class="token punctuation">,</span> <span class="token keyword">async</span> context <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">var</span> <span class="token punctuation">(</span>sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FromBody</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RegisterProductRequest<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> productId <span class="token operator">=</span> Guid<span class="token punctuation">.</span><span class="token function">NewGuid</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> RegisterProduct<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span>productId<span class="token punctuation">,</span> sku<span class="token punctuation">,</span> name<span class="token punctuation">,</span> description<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SendCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">Created</span><span class="token punctuation">(</span>productId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> endpoints<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Along with a few simple extensions:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">HttpExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">FromBody</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">HttpContext</span> context<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">ReadFromJsonAsync</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">??</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentNullException</span><span class="token punctuation">(</span><span class="token string">"request"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">Created</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">HttpContext</span> context<span class="token punctuation">,</span> <span class="token class-name">T</span> id<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span><span class="token punctuation">?</span></span> location <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>Headers<span class="token punctuation">[</span>HeaderNames<span class="token punctuation">.</span>Location<span class="token punctuation">]</span> <span class="token operator">=</span> location <span class="token operator">??</span> <span class="token interpolation-string"><span class="token string">$"</span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">context<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>Path</span><span class="token punctuation">}</span></span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">id</span><span class="token punctuation">}</span></span><span class="token string">"</span></span><span class="token punctuation">;</span> <span class="token keyword">return</span> context<span class="token punctuation">.</span><span class="token function">ReturnJSON</span><span class="token punctuation">(</span>id<span class="token punctuation">,</span> HttpStatusCode<span class="token punctuation">.</span>Created<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token generic-method"><span class="token function">ReturnJSON</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">HttpContext</span> context<span class="token punctuation">,</span> <span class="token class-name">T</span> result<span class="token punctuation">,</span> <span class="token class-name">HttpStatusCode</span> statusCode <span class="token operator">=</span> HttpStatusCode<span class="token punctuation">.</span>OK<span class="token punctuation">)</span> <span class="token punctuation">{</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>StatusCode <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>statusCode<span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>result <span class="token operator">==</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span><span class="token function">WriteAsJsonAsync</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">CommandHandlerExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ICommandHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">GetCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">HttpContext</span> context<span class="token punctuation">)</span> <span class="token operator">=></span> context<span class="token punctuation">.</span>RequestServices<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetRequiredService</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ICommandHandler<span class="token punctuation">&lt;</span>T<span class="token punctuation">></span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ValueTask</span> <span class="token generic-method"><span class="token function">SendCommand</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">HttpContext</span> context<span class="token punctuation">,</span> <span class="token class-name">T</span> command<span class="token punctuation">)</span> <span class="token operator">=></span> context<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetCommandHandler</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>T<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Handle</span><span class="token punctuation">(</span>command<span class="token punctuation">,</span> context<span class="token punctuation">.</span>RequestAborted<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>It allows us to achieve a short and straightforward code. Additionally, we have full control over whether we want to do specific support for a given endpoint (e.g. some HTTP headers etc.). We don’t lose much from the ease of use, as we can inject our handler via IoC. We can wrap it with a decorator or do other stuff if we need it.</p> <p>We also gain efficiency because we do not do redundant mappings, IFs, etc.</p> <p>However, the <em>DRY</em> (<em>Don’t Repeat Yourself</em>) principle has been hammered into our heads over the years. We may want to go further: wrap it up extra to do a one-liner for command registering:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp">endpoints<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">MapCommand</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>RegisterProduct<span class="token punctuation">></span></span></span><span class="token punctuation">(</span>HttpMethod<span class="token punctuation">.</span>Post<span class="token punctuation">,</span> <span class="token string">"/api/products"</span><span class="token punctuation">,</span> HttpStatusCode<span class="token punctuation">.</span>Created<span class="token punctuation">)</span></code></pre></div> <p>Of course we can do this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">EndpointsExtensions</span> <span class="token punctuation">{</span> <span class="token keyword">internal</span> <span class="token keyword">static</span> <span class="token return-type class-name">IEndpointRouteBuilder</span> <span class="token generic-method"><span class="token function">MapCommand</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TRequest<span class="token punctuation">></span></span></span><span class="token punctuation">(</span> <span class="token keyword">this</span> <span class="token class-name">IEndpointRouteBuilder</span> endpoints<span class="token punctuation">,</span> <span class="token class-name">HttpMethod</span> httpMethod<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">string</span></span> url<span class="token punctuation">,</span> <span class="token class-name">HttpStatusCode</span> statusCode <span class="token operator">=</span> HttpStatusCode<span class="token punctuation">.</span>OK<span class="token punctuation">)</span> <span class="token punctuation">{</span> endpoints<span class="token punctuation">.</span><span class="token function">MapMethods</span><span class="token punctuation">(</span>url<span class="token punctuation">,</span> <span class="token keyword">new</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">{</span>httpMethod<span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">}</span> <span class="token punctuation">,</span> <span class="token keyword">async</span> context <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">FromBody</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TRequest<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> commandResult <span class="token operator">=</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">SendCommand</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>commandResult <span class="token operator">==</span> CommandResult<span class="token punctuation">.</span>None<span class="token punctuation">)</span> <span class="token punctuation">{</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>StatusCode <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>statusCode<span class="token punctuation">;</span> <span class="token keyword">return</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">await</span> context<span class="token punctuation">.</span><span class="token function">ReturnJSON</span><span class="token punctuation">(</span>commandResult<span class="token punctuation">.</span>Result<span class="token punctuation">,</span> statusCode<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> endpoints<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>However, it forces us to immediately change the interface to the command:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">ICommandHandler<span class="token punctuation">&lt;</span><span class="token keyword">in</span> T<span class="token punctuation">></span></span> <span class="token punctuation">{</span> <span class="token return-type class-name">ValueTask<span class="token punctuation">&lt;</span>CommandResult<span class="token punctuation">></span></span> <span class="token function">Handle</span><span class="token punctuation">(</span><span class="token class-name">T</span> command<span class="token punctuation">,</span> <span class="token class-name">CancellationToken</span> token<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">record</span> <span class="token class-name">CommandResult</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">object</span><span class="token punctuation">?</span></span> Result <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token function">CommandResult</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">?</span></span> result <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token operator">=></span> Result <span class="token operator">=</span> result<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">CommandResult</span> None <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">CommandResult</span> <span class="token function">Of</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">object</span></span> result<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token keyword">new</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Additionally, problems are popping up:</p> <ul> <li>Where to generate id? Should we move it to the command handler?</li> <li>HTTP status <em>Created</em> should return the Location header. How to do it? Add if in the mapping?</li> </ul> <p>As the application grows, we’ll have more and more such requirements. The number of tweaks to our generic code will grow. Either we have to add some (auto) mappings or reflection usage. What do we gain from that? Shortening handlers by 10 lines?</p> <p>We also lose clarity because we cannot directly see top-down what is happening in our codebase. By that, we did a full circle, and we came back to the place we wanted to get away from. A change in such defined <em>MapCommand</em> can, in an extreme case, crash all endpoints.</p> <p>Do you think it’s worth it or not?</p> <p>I prefer <em>KISS</em> - <em>“Keep It Simple, Stupid”</em> instead of <em>DRY</em>.</p> <p>I encourage you to take a look at:</p> <ul> <li>the whole sample: <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/Warehouse">https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/Warehouse</a>,</li> <li>PR with “the extreme MapCommand makeover”: <a href="https://github.com/oskardudycz/EventSourcing.NetCore/pull/43">https://github.com/oskardudycz/EventSourcing.NetCore/pull/43</a>.</li> </ul> <p>For more information about CQRS check my other articles:</p> <ul> <li><a href="/en/cqrs_facts_and_myths_explained/">“CQRS facts and myths explained”</a></li> <li><a href="/en/how_to_slice_the_codebase_effectively/">“How to slice the codebase effectively?”</a></li> <li><a href="/en/can_command_return_a_value/">“Can command return a value?”</a></li> </ul> <p>Comments are welome!</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Structural Typing in TypeScript]]>https://event-driven.io/en/structural_typing_in_type_script/https://event-driven.io/en/structural_typing_in_type_script/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/960ddfe911bf6d4a6ccc9c80aab66134/d2429/2021-06-09-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9ADEuXiYmWCgnWSkoU6mktoB9oRUVPGJUVl9OPxkRDBMKCDwnHnVSREIoHkMwJE03K6GKeeDNu9C8rNbDswCShJOIgp9TTnt7c4vGxttMTHMTGj0pIScxIhcIBgQcDwtxRjmyenOse3hPMio5JBmDa1vl0cDax7fXw7AAv7W52Nvi4+fo3N/lh4ekGCFPHSA4OCcdIRYOBQQELhoShldHoWlfuYF5kWBWLBQNcFpKzrqo3Mq70L2uAKGWld/g5PHx8KObpEE+VhkeOC0iHz4oGRoRCwQEAkMmG4JQRIZQQohTRZZhU0spHzgkFriikubUxdTBswBJODGYi4SVi4k4MjwkJi4LCQwiFg0+KxwhFg4CAgBVMCSKUEN+SUCubmesb2NVMic3JhuDbV2mkIHItKUATz42UT0wXkg4Wkg6HB4eCAkJDggELBwSMB8UCwgFTSwfckY4dkg+rXBoqGtfVDYtGQ4IMB4UQSwgSjctAE47MEk2KlA8LWdPPEY4Lg0LCAUFBAgFAyUXDyIVDWZMQYNWS39EPJ5nXppwaC0fGxsOCCkZESYZEScZEgAwHhUkFQ4dEQ0tHhYtHRUuHxgvIhsXEAsQCwcbDwhOOjKadG+eaGW+kZB9ZGItGBZpQT+IV1WIWVpQMy8ANSEWKxsSOCUZOyYZJhYNOigbPi0iKxwULx8TGxILHhAKaFFIknRuaFdWHA8PdkdElV5bl2Fcj1dTeEVEAFc7KlQ5KFQ5J041JkkxIl1CMFE8LSYZEi8fFiYaEiIUDysZEDMkHRAKBzQaFGU7OlszMJVbVWo9NSAODABwUUJyU0JwUT53V0VyU0JrTj1eQzI3JxsxIBkrHhcjFxE4IxonGBIZEA1BIx5YLCpsPDhHJiMhDw4RCgsOT8CD3RhHcwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/960ddfe911bf6d4a6ccc9c80aab66134/a331c/2021-06-09-cover.png" srcset="/static/960ddfe911bf6d4a6ccc9c80aab66134/36ca5/2021-06-09-cover.png 200w, /static/960ddfe911bf6d4a6ccc9c80aab66134/a3397/2021-06-09-cover.png 400w, /static/960ddfe911bf6d4a6ccc9c80aab66134/a331c/2021-06-09-cover.png 800w, /static/960ddfe911bf6d4a6ccc9c80aab66134/d2429/2021-06-09-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>When we talk about typing in programming languages, we usually divide it into static and dynamic. Static typing is checked at the compile-time, e.g. in Java, C#, C++. Dynamic typing is checked when the code is run, e.g. in Python or JavaScript.</p> <p>Both types of typing have their advantages and disadvantages.</p> <p>Dynamic typing allows you to write more concise code. We can make any transformations as long as their effect works as expected at the end. This allows simplifying the code and cut ceremony but requires more knowledge (or luck) if you want to do it well. You can obviously help yourself using static code analysis (all kinds of linters, analyzers, etc.). The must-have is also a decent set of tests.</p> <p>Static typing allows you to recognize basic errors at the compile-time, e.g. incorrect type assignment, lack of required fields, or even a stupid typo in the field name. It’s easier to do refactoring because we can immediately see if we broke something. It also easier to manage quality in teams with various levels of experience. In theory, types and compilation will save us from stupid mistakes. However, we also often have to flex ourselves to please the type system. Often, more code needs to be generated. There is always something for something.</p> <p>Here we get to the subject of this article. What is structural typing?</p> <p>Static typing can be divided into <em>nominal</em> and <em>structural</em>.</p> <p>In nominal typing, when assessing whether a given object is of a specific type, we verify:</p> <ul> <li>type name,</li> <li>fields’ presence,</li> <li>fields’ names,</li> <li>fields’ types.</li> </ul> <p>Structural typing do not care about the type name. It checks whether the object’s structure agrees with the pattern restrained by type definition (fields’ presence, names and types). We take the type template and try to fit the object in it. If we manage to do that, then the object is of a given type. It can also be compared to the definition of a human being. A human is a mammal; it has legs, arms and head. However, if the definition is too general, a monkey can also be considered a human. After all, it is also a mammal; it has legs, arms and head.</p> <p>Look below:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">interface</span> <span class="token class-name">Human</span> <span class="token punctuation">{</span> legs<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> hands<span class="token operator">:</span> <span class="token builtin">number</span> <span class="token punctuation">}</span> <span class="token keyword">interface</span> <span class="token class-name">Employee</span> <span class="token punctuation">{</span> legs<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> hands<span class="token operator">:</span> <span class="token builtin">number</span><span class="token punctuation">,</span> name<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> johnDoe <span class="token operator">=</span> <span class="token punctuation">{</span> legs<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> hands<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> name<span class="token operator">:</span> <span class="token string">"John Doe"</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> ape <span class="token operator">=</span> <span class="token punctuation">{</span> legs<span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> hands<span class="token operator">:</span> <span class="token number">2</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> snake <span class="token operator">=</span> <span class="token punctuation">{</span> tounge<span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token comment">// OK</span> <span class="token keyword">let</span> human<span class="token operator">:</span> Human <span class="token operator">=</span> johnDoe<span class="token punctuation">;</span> <span class="token keyword">let</span> employee<span class="token operator">:</span> Employee <span class="token operator">=</span> johnDoe<span class="token punctuation">;</span> <span class="token comment">// OK</span> human <span class="token operator">=</span> ape<span class="token punctuation">;</span> <span class="token comment">// Fail - missing name</span> employee <span class="token operator">=</span> ape<span class="token punctuation">;</span> <span class="token comment">// Fail - missing legs, hands</span> human <span class="token operator">=</span> snake<span class="token punctuation">;</span> <span class="token comment">// Fail - missing egs, hands, name</span> employee <span class="token operator">=</span> snake<span class="token punctuation">;</span></code></pre></div> <p>As you can see, variables definitions do not have any type name. Typescript compiler does not need that. It only checks if the object structure matches the type definition. It may look strange at first, but it offers a lot of possibilities. If, for example, we have a function to display the name, we do not have to create an additional interface and have each class implementing it. We can just do this:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">printName</span><span class="token punctuation">(</span>name<span class="token operator">:</span> <span class="token punctuation">{</span> firstName<span class="token operator">:</span><span class="token builtin">string</span><span class="token punctuation">,</span> lastName<span class="token operator">:</span> <span class="token builtin">string</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token builtin">console</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token template-string"><span class="token template-punctuation string">`</span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token punctuation">.</span>firstName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>name<span class="token punctuation">.</span>lastName<span class="token interpolation-punctuation punctuation">}</span></span><span class="token template-punctuation string">`</span></span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>This allows for a lot of ceremonies, especially if we write in a more functional style.</p> <p>Like everything, it has its advantages and disadvantages. A lot of people who come from the world of _ “nominal typing” _ (read C#, Java) don’t try to understand that TypeScript is a language of a different (<em>nomen omen</em>) type. They try to forcefully transfer their preferences and cram interfaces and classes everywhere. They limit the room for manoeuvre and reduce the programming environment to the lowest common denominator.</p> <p>Never mind if it is just dragging ballast. That might be someone’s preference. The problem starts when one forgets that TypeScript is not a compiled language - it is a transpiled language. TypeScript doesn’t enforce types at runtime. It just validates them and translates them to JavaScript code. Once this is done and you run your code, it is already dynamically typed JavaScript. Paper approves of anything, and so will JavaScript.</p> <p>The worst thing we can do is handle the request like this:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">addEmployee</span><span class="token punctuation">(</span>user<span class="token operator">:</span> Employee<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>user<span class="token operator">?.</span>name <span class="token operator">&amp;&amp;</span> user<span class="token operator">?.</span>legs <span class="token operator">!==</span> <span class="token number">2</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>user<span class="token operator">?.</span>hands <span class="token operator">!==</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">"Not an Employee"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">saveToDatabase</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Why is it so bad? We have validation. Everything looks correct. Well no. In fact, underneath (especially after deserialization), our object may have additional fields, the structure may be extended - e.g. someone can send:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token property">"name"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"firstName"</span><span class="token operator">:</span> <span class="token string">"John"</span><span class="token punctuation">,</span> <span class="token property">"lastName"</span><span class="token operator">:</span> <span class="token string">"Doe"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"legs"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token property">"hands"</span><span class="token operator">:</span> <span class="token number">2</span><span class="token punctuation">,</span> <span class="token property">"iWillSpamYourDB"</span><span class="token operator">:</span> <span class="token string">"someExtremelyLargeText(...)"</span> <span class="token punctuation">}</span></code></pre></div> <p>And if we’ll just deserialize and assume that we know what the type is, we’ll be shocked. Therefore, the first thing I suggest doing is rewrite the fields and create a new object after validation. We can trust the objects in our code (let’s say), never the external ones.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">function</span> <span class="token function">addEmployee</span><span class="token punctuation">(</span>userRequest<span class="token operator">:</span> <span class="token builtin">any</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>userRequest<span class="token operator">?.</span>name <span class="token operator">&amp;&amp;</span> userRequest<span class="token operator">?.</span>legs <span class="token operator">!==</span> <span class="token number">2</span> <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span>userRequest<span class="token operator">?.</span>hands <span class="token operator">!==</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token string">"Not an Employee"</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> user<span class="token operator">:</span> User <span class="token operator">=</span> <span class="token punctuation">{</span> name<span class="token operator">:</span> userRequest<span class="token punctuation">.</span>name<span class="token punctuation">,</span> legs<span class="token operator">:</span> userRequest<span class="token punctuation">.</span>legs<span class="token punctuation">,</span> hands<span class="token operator">:</span> userRequest<span class="token punctuation">.</span>hands <span class="token punctuation">}</span> <span class="token function">saveToDatabase</span><span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>This may seem redundant, but it gives us confidence in our code and types plus protects us from unsafe behaviour.</p> <p>Personally, I think structural typing is excellent. It simplifies our life and gives us superb opportunities.</p> <p>When we start working in a new environment, let’s tackle it open-minded We should not take the easy way and do not try to blindly transfer our habits, but understand the conventions and differences. <a href="https://www.youtube.com/watch?v=532j-186xEQ">Because it may turn out that these are not the droids we’re looking for</a>.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Check also my post about <a href="/en/partial_typescript/">why partial types are neat in TypeScript</a>.</p><![CDATA[When Agile is not enough]]>https://event-driven.io/en/when_agile_is_not_enough/https://event-driven.io/en/when_agile_is_not_enough/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/fcb3291965b7753bb77b8916cecf251d/d2429/2021-06-02-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABrklEQVQoz31RXU/bMBTtj560l0l7B208TdqYtudpEgwxJkopQmRkZV0bSEeTwEjjkiYNofkgX8SO7TsRl8I0bUd+uD72ub7nuAE1qqrKS5yWJeUcFuAcGIO/QYjgG2J7q+ujL2uRbsT6z5mpAcCVh5IsBgDft0PfhiQlY+QcNLF6DAj9IWaMXlkDHoUpuvC7EqDxtPUp+SHD2E7ltr/fhCCkwWx02iWULCaYiyuMI2dKgiD9ZXqSHBx1dpdffFt96+7sbD55evjsuXcoO+sb/aUV9dUqJZUw1RAWU1W11taz4dCWvrqqqul6e7upDAYGQs1W68IwJqdD8/OWvbGJPnwsFAUwfni5zPMouclxyQAo54zRujVnVTUvAPj9qiiFOtf7sSnLsoJgLFguUKfCGRNFkcR5NHucf4PXt7Oba8caCGpiqnfx1nAnZ1HgitpGmjVS51/4WByH1+fa8V37orgcGZ5zWTFO4sSU9iYdGQchSZJpr3+2tV3Y7kI/F0dRPNQMALAsJHe+93r99Lb0lJODN++PXr+zuj1POdlbXpGWXp6394WvB8//Av/v6W9I82IysUytEwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/fcb3291965b7753bb77b8916cecf251d/a331c/2021-06-02-cover.png" srcset="/static/fcb3291965b7753bb77b8916cecf251d/36ca5/2021-06-02-cover.png 200w, /static/fcb3291965b7753bb77b8916cecf251d/a3397/2021-06-02-cover.png 400w, /static/fcb3291965b7753bb77b8916cecf251d/a331c/2021-06-02-cover.png 800w, /static/fcb3291965b7753bb77b8916cecf251d/d2429/2021-06-02-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Today I want to invert <a href="https://blog.crisp.se/wp-content/uploads/2016/01/Making-sense-of-MVP-.jpg">the classical pro-agile drawing</a>. Let’s talk about the devaluation of functionality that occurs in Agile projects.</p> <p><strong>In the old times, the project was planned ahead. The Waterfall was the king.</strong> First, analysts were with the client, collecting requirements and building the system vision. The next phase was functional and technical analysis. Plans were made, steering committees were created. Having several hundred (thousand?) pages of documentation, you could start programming. The developers didn’t see the client face-to-face. What’s more, they sometimes did not even know what they were producing. They got requirement #284 located on page 1847 in the documentation. Just create a form that will call the service with the defined request params. Then repeat and repeat for similar specifications. After years of analysis and coding, finally, testing can be done. A lot of different tests: system, user acceptance etc. Then the long-awaited deadline. That’s an optimistic scenario. Much often the project was so off the budget that it collapsed in the meantime.</p> <p>At one point, the projects were so delayed and their process so difficult to handle that people wondered how the software could be delivered faster and more predictably. That is, allow working in shorter cycles. Thanks to that, the clients could get functionalities quicker, evaluate them, and give feedback faster. They could also judge that this feature is “good enough”. That gave the possibility to change requirements and make the decision based on the current conditions. <strong>That’s where Agile was born.</strong> SCRUM became the most popular Agile methodology. Right now, all projects are agile, don’t they?</p> <p><strong>By the time it appeared that using SCRUM was not solving all the issues that it was promising.</strong> It was still hard to understand what the project state was and when the features would be delivered. However, few people remember that SCRUM in its assumptions did not mean that we stopped planning the entire product. It’s not like we should only prepare for the next sprint. What is an essential element of SCRUM is Global Backlog. This is a general list of tasks that are needed to complete the whole system.</p> <p><strong>It’s not that agile projects should go on forever (although maybe software shops would like to).</strong> SCRUM (and Agile in general) is about tasks’ re-evaluation and re-prioritization with each iteration. It’s all about being flexible about the way to achieve your goal. But! That goal should still be there. We need to take an azimuth to coordinate the team and keep awareness of the requirements. With each sprint, we should better assess how much time it will take to complete the rest of the system.</p> <p>It’s not that agile methodologies are focused on chasing a bunny, and “heavier” like Waterfall or PRINCE2 on catching it.</p> <p><strong>Unfortunately, in our projects, we often forget about their purpose.</strong> We live from sprint to sprint. We forget that the effect of a single sprint does not make any sense as long as it does not bring us closer to completing the project. We have our little battles ignoring that we have the entire war to win. Moreover, we often forget that some things cannot be built iteratively.</p> <p><strong>It’s not that when our client ordered a car, we first delivered roller skates, then a scooter, then finally a car.</strong> If clients want a car, we have to build a car. Unfortunately, some things have to take longer. Making a chassis and attaching wheels to it will not make it something to present to the customer. Showing that we have a scooter and checking whether clients didn’t change their minds and will be happy to use it instead of a car is often not the best approach.</p> <p>What do I mean by that? <strong>We need to remember what is most important, finishing the project.</strong> Dividing time into sprints doesn’t make us agile. The fact that we completed the sprint does not matter if the project is not finished. Sometimes, it is worth accepting that certain things must last and not finish something in one sprint. We should not be trying to forcibly change the requirements or cut them into smaller pieces to fit them into the framework of these 2-3 weeks. I know that it is always motivating to finish all the stories in a sprint. Still, the most important thing is to deliver what the client is expecting. By forcefully adjusting the scope and producing our partial effects, we may waste time managing tasks and building the same thing several times.</p> <p>It’s also crucial to keep the fast feedback loops. Working with the client is the foundation for Agile software development. If we don’t have it, it will be Waterfall hidden behind the scenes or Jira Oriented Programming.</p> <p>Remember that Agile methodologies are… er… Agile! It’s all about being flexible, remembering the customer’s needs and what the system is supposed to do.</p> <p>Therefore, as every week, I encourage pragmatism, common sense and questioning what you hear or read. For example, what I wrote above.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[How to scale projections in the event-driven systems?]]>https://event-driven.io/en/how_to_scale_projections_in_the_event_driven_systems/https://event-driven.io/en/how_to_scale_projections_in_the_event_driven_systems/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/d7d250f5a2ffb2e66bc3732b6307a0e4/d2429/2020-05-26-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACpklEQVQozxXN+08ScQAA8PvNNROdzdWvucXyseG0tbZMfGsq4tRSwxcCieCDEBDwBgJ3wD3ggPveAaaEx3jIgZCl87G25mTTrT/A/ousX+y31ucf+EDpZDqVSLgcrngsBlthJgRCgSAIBkkMcyNIwO/fiUS2w1E7DNth+GM0SmK4Hd406o0RNgyxgAkFQsV8IcKwFOnTraySGO52IQ67PcKE13V6yudnQsBm3SRxfHlJo1pQyCYnIixj2bBAeT63qtWCEHBtOQ9yOdSJGNcN2cy+1WzdiW6fn54lOS7op7xuz4bBaNDrzUYjQwOVQmnUr0Px3ZhKLkddyIc1XYgKoE6Xj/QlE8nrq6stmy3P83medyNoPst7PR5+f58ifVq1RilX9Hb1QH6CmJPJKJLyoG4QAr9vb4PU/6eYL8RjMRLHPx8U0qnU1y+HpcsSSRBuBDV80JtNG53tHZBGrTGbTFycU6sWSZzIZ/lkIsHtcX6C+Hlz8/fuzmGz+QginUzls9mA3899is/Ipr0o8uP6Gvpz+wtzI4SXoHwk4kI9CHpydFy6LIVpwMXjmVQqzIBiscjS4PT4iKFBpUAgn51VL6pbRCJI3PpK1NjotDkKuUJsN8YGwUGWPywWhwYHCC+umJ//dn6WyWSWNVoSw81GEwtoBoDujq6hvn6oUyxuFom4PY7EsIaG+td9/bOy6YvvF6cnJ3SQtlksLE2HAVMnFFaUl7eLxTRFw2arUq4YGx6G2lrbhgclb8fGmUCwpUl0r6ysprp6eWlFKpFiHi/h9m4zbCwchQ0G2dibp0+E4yOjvd09tY9rqwSVUHPTM+mgdGRA8n5BKX7xcqR/qFFYp1tZg63WSoEAgqCG+vrJicnRAcnzpuaa6gczU1Nz72Ye1Tysul/xD49gQQPDFH/4AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/d7d250f5a2ffb2e66bc3732b6307a0e4/a331c/2020-05-26-cover.png" srcset="/static/d7d250f5a2ffb2e66bc3732b6307a0e4/36ca5/2020-05-26-cover.png 200w, /static/d7d250f5a2ffb2e66bc3732b6307a0e4/a3397/2020-05-26-cover.png 400w, /static/d7d250f5a2ffb2e66bc3732b6307a0e4/a331c/2020-05-26-cover.png 800w, /static/d7d250f5a2ffb2e66bc3732b6307a0e4/d2429/2020-05-26-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>People want to scale up everything. In the past, the recipe for everything was to buy a larger server. Today, the answer is to add another instance. Does it always make sense?</p> <p><strong>“Will it scale?”</strong> is not a question, it is a mantra. We often ask about it even if we don’t know if our solution needs to scale. We’re preparing for that, just in case. Then we’re angry hearing “It depends”. There is no single scaling axis: those decisions need to be based on hard numbers and performance requirements. Based on that information, we can select the right strategy.</p> <p><strong>This is not different from the idea of scaling projections in event-driven architectures.</strong></p> <p><a href="/en/projections_and_read_models_in_event_driven_architecture/">A projection is an interpretation of a set of events.</a> It is often used to create a read model. We take the current state, apply the event data on it and store the mutated state. The projection result can be stored as a row in the relational database or other prefered type of storage (you can read more about that in my other article, <a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">“How to create projections of events for nested object structures?”</a>).</p> <p>We can come to the conclusion that it might be worth running the projection logic in parallel. That looks like a quick boost to our events processing. As we know, premature optimisation may be the root of all evil. Let’s discuss that in more detail. It might not be as trivial and obvious as it may look at first glance.</p> <p>What are we to optimise?</p> <ul> <li><strong>database writes</strong> - upserts in databases are typically fast. Also, if we’re doing our writes sequentially, we’re not risking getting deadlocks by concurrent writes on the same record. If we’re facing a bottleneck on the write side, we either have a high load or an issue in our database structure.</li> <li><strong>processing</strong> - we can have a plethora of issues here. We should verify if our networking configuration and multi-region setup is valid. We can also check if scaling vertically won’t be enough and would cost less. Higher latency might not be caused by our application code but by how it’s configured.</li> </ul> <p><strong>If we benchmarked the current solution, compared to the expected and concluded that we need to scale, I’d recommend considering the partitioning/sharding strategy.</strong> The most obvious approach is ensuring that the single projection type will be handled by the specific subscriber (this can be done by, e.g. listening for the events handled by this projection type or per stream type). Having that, we can distribute the load and not end up with the competing consumers’ problem (e.g. RabbitMQ uses this approach).</p> <p>If consumers compete for the shared resource (e.g. the read model we’re writing to), then it’s hard to guarantee processing order. In theory, we could write a stream version of the projection and verify if it’s higher than the version from processing events. However, we might end up in this scenario that:</p> <ul> <li>the first handler gets events 1 and 2</li> <li>the second handler gets event 3.</li> </ul> <p>If the first handler lags, the second will write the projection and set the version to 3. If we perform a check based on the version, then events 1 and 2 will be ignored.</p> <p><strong>Also, with competing consumers, events get out of order (e.g. because of retries).</strong> We may end up with version number gaps and not easily verify if it’s okay to write or not.</p> <p>It can work if your events are typical transport events or inform about “upserts”. The sequence of events is not always critical. Sometimes they are transport events that keep arriving (for replication). Such events usually do upserts without any logic at the end. We can then be optimistic that if the situations are sporadic, “someday it will level out”. However, losing an event may be critical for typical business events.</p> <p><strong>The other scenario is when you don’t care about ordering and can live with data for some time in the potentially wrong state</strong> (e.g. at first, apply what’s possible from the “update event”, then fill with the data from “create event”). However, that usually requires additional effort in projecting business logic and ensuring that out-of-order cases are adequately handled. The downside is that we have to accept that our data may be incomplete. It is not always a problem. By definition, we should not make business decisions based on the read model. Well, if the situations are repeated often, it can be a pain for the user. Plus, the programmer has to code all these correlations.</p> <p><strong>Processing events in parallel is challenging for projections from a single stream.</strong> It gets even more tricky for projections from multiple streams (e.g., different topics or partitions in Kafka). Usually, in this case, we have no guarantee that the data from different partitions will have the appropriate time correlation. You might need to maintain different versions for the other streams in the same read model, which is not manageable in the long term.</p> <p><strong>I recommend starting with a single writer.</strong> If you see performance issues, define your partitioning/sharding strategy and ensure you don’t have competing subscribers for the same target read model. It’s also essential to make your projections idempotent (so if you get the same event twice, it will have a single effect).</p> <h2 id="here-are-a-few-tips-to-consider" style="position:relative;"><a href="#here-are-a-few-tips-to-consider" aria-label="here are a few tips to consider permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>Here are a few tips to consider:</h2> <ol> <li><strong>Consider batching writes.</strong> If the storage engine is the bottleneck, then it’s worth grouping the updates by the read model result. It may appear that we won’t need to put multiple writers and make our solution more complex by doing that. For example, it will be too slow if you try to apply events individually to the ElasticSearch index (around 10 updates per second). If you batch it, it easily exceeds 1000 per second. The important thing to note is that even if we’re batching the changes, we should make sure that operations within the batch are performed in the proper order. Running operations in parallel may add non-deterministic behaviour. It’s worth double-checking the storage operation capabilities before running batches. Read more in <a href="/en/projecting_from_marten_to_elasticsearch/">Projecting Marten events to Elasticsearch</a></li> <li><strong>Make your projections idempotent</strong>. Most messaging system gives you an <a href="/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">at-least-once delivery guarantee</a>. That may happen when the failure occurs, and we need to retry processing. If that happens, our projection logging will be triggered more than once. We must include it in our projection logic to ensure we won’t have side effects. There are multiple techniques on how to deal with that. Read more in <a href="/en/simple_trick_for_idempotency_handling_in_elastic_search_readm_model/">A simple trick for idempotency handling in the Elastic Search read model</a>, <a href="/en/dealing_with_eventual_consistency_and_idempotency_in_mongodb_projections/">Dealing with Eventual Consistency and Idempotency in MongoDB projections</a>.</li> <li><strong>Use one worker instance to build a read model.</strong> Projections usually should not have extensive logic and should do a simple record change. It should be fast by design. When it does not have to compete for resources, a single instance will be faster to act and write than complex multithreaded code. That’s how Node.jS was created. The authors found that multithreading is overrated. It might be better to queue tasks than to share them. It also makes sense in our daily activities; will you do something faster when you try to do multiple things at once or do them individually? Single writer instance can take you pretty far if you put the business logic in the write model and then just process events and do simple state updates in projections.</li> <li><strong>Invest in data partitioning and sharding.</strong> A clone war for resources will not be necessary if we partition services by, e.g. read model type, tenant, event stream, and end storage target (document/table). By doing that, we will have a set of independent instances focused on handling a specific set of events and not fighting for the order they need to be processed. Processing will still be simple, and we’ll have a single writer per shard.</li> </ol> <p><strong>And here we come back to the mythical question. Will it scale?</strong> It does not have to scale. If it has to, adding new instances does not always make sense. We should do it wisely. Load balancing, sharding and partitioning are the basics that also work in today’s world of microservices. Using messaging tools and databases properly is based on choosing the correct partitioning key.</p> <p><strong>We will not achieve well-scaled systems without thinking about how our data relates to each other and partitioning it.</strong> Caring about basics, it’s not as sexy as launching a new Kubernetes deployment, but it’s necessary.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Read also <a href="/en/projections_and_read_models_in_event_driven_architecture/">Guide to Projections and Read Models in Event-Driven Architecture</a> to learn more about other concepts to keep in mind.</p><![CDATA[Why are senior devs afraid to code?]]>https://event-driven.io/en/why_are_senior_devs_afraid_to_code/https://event-driven.io/en/why_are_senior_devs_afraid_to_code/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/952288fcb3f5016f80f57231847f4fa6/d2429/2020-05-19-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACfklEQVQozx3LS2/SAAAA4J6Mi4s4HKBdYYyHvAqFAqW0lNJCobQUSoECBVoce4WH4sYcGSMao1mMaOL0YNSbB/+mid/9A44VKk+GNR5pCklREYmqjBYKYYpEWBbOZlCGDFCUE0UPkCDG0WghOzTKHy/qpUbhtkUAU5VS+fghD4u5oJyHi2IumqMCRMqPY41K6m6enw45bxKlq5ysSb1e5XLSWEwbuMjeyglg1qE2Y9YQw0TCzdFBmvBrSq5UK4qV7HrK/1qLn+a8UM1VOlVRZt6P+fNx7cNVyxiVNzUEmKj09xk76RHewB4ScVBpXzbl6Zdjoxa1nJVXc/56wgvVLF8vNtX85Sh/Pql/XvWuz+VvVRiYabmbw+x8kIajrkAAeuYDmUyAzoQ1Pmao1JGRH/Y5Mo+zAlNvl8Z64cWZ8vZqsFl1/nZRYKHTpx3ipJvWGxiKusiYMwI7SmwsEPWn0yFJwHAinKainJCtyEy/U1yM6zcX7XeL1t9uHFjolC4nqwJqtPC2hHo81hzuS5ERu8/h8IKWfdt9i4lJ7I/lOMOTTbU4PVHeXKhfluqf9v98quLtWkqTk2daBkl68eiBwqE2n8tst5oh67bFZNu3dAnnohIatOjRkbyaNX6uu79rIeDVgNIVbKqRqhSvi/F+k/IG7W6/w+KCTODuYyf4wGKyufYe2Z+QMHjTSS11ajxg1zNlw/uBY5VsSfFhAzvTCI4NK3KmzGM7kHVrZ9sMWXed4L2dbSbp49m4yQmFEO9SCn3tIS+N/OtCEGiKqXYtqQjowqClcqJUTDzv8xHUv2V+CHocZsgKuiGn++mwEK5zmAv2Wg7AU9r9Q0/cSfA/Hu6zBI9A0TIAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/952288fcb3f5016f80f57231847f4fa6/a331c/2020-05-19-cover.png" srcset="/static/952288fcb3f5016f80f57231847f4fa6/36ca5/2020-05-19-cover.png 200w, /static/952288fcb3f5016f80f57231847f4fa6/a3397/2020-05-19-cover.png 400w, /static/952288fcb3f5016f80f57231847f4fa6/a331c/2020-05-19-cover.png 800w, /static/952288fcb3f5016f80f57231847f4fa6/d2429/2020-05-19-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>The job interview is a stressful situation, usually for both parties. Some time ago, I had to recruit senior developers. I had the idea to ask a set of elementary questions. That type of question you get the solution in the first link after googling about the technology. I hoped that the interviewees would answer them and feel more relaxed, and better show their knowledge when asked more complex questions. You can probably guess where I am going with it: most of those people failed on those basic questions.</p> <p>Recently, self-help books and biographies have become popular. I don’t read too many of them. I have the impression that there is too much promotion of authors and not enough specific recipes. I prefer good fiction books such as “To Kill a Mockingbird”, “1984”, “Cat’s Cradle”. I believe that you can learn more about life from them than from guidebooks. Some time ago, I decided that maybe it’s time to catch up and check if I’m not mistaken. I managed to find some valuable ones like <a href="https://www.amazon.com/Atomic-Habits-Proven-Build-Break/dp/0735211299/">Atomic Habits</a> or <a href="https://www.amazon.com/Company-One-Staying-Small-Business-ebook/dp/B078962RHQ/">Company of One</a>. However, for most others, I believe that if they were condensed into a longer blog post, they would have much more value than the artificially packed books.</p> <p>One such book is <a href="https://www.amazon.com/Mindset-Psychology-Carol-S-Dweck/dp/0345472322">“Mindset: The New Psychology of Success” by Carol Dweck</a>. For over 200 pages, the author in many ways grinds her theory that people can be divided into two mindsets:</p> <ul> <li>fixed,</li> <li>growth-oriented.</li> </ul> <p>Roughly speaking, <em>fixed mindset</em> people believe that every human being has an inborn set of abilities. They think that if you achieve something, you have a talent for that. Otherwise, you just don’t. They think you are unlikely to change if you don’t have the natural skillset.</p> <p>People with <em>growth mindset</em> believe that everything can be improved and developed.</p> <ul> <li><em>“What do you have to do to play Carnegie Hall?”</em></li> <li><em>“Practice a lot”</em>.</li> </ul> <p>Of course, they are not dreamers who believe that they can always succeed. They just think that if you work on something regularly, you will be better at it (which coincides with the message of the book mentioned above <a href="https://www.amazon.com/Atomic-Habits-Proven-Build-Break/dp/0735211299/">Atomic Habits</a>). Routine and practice can build your skillset. All 200 pages can be summarised in that paragraph.</p> <p>Why am I writing about this? There is an interesting observation in the book about people who are <em>“fixed”</em>. If they achieve some level of success, they will want to keep it. What’s wrong with that is the way they will try to do it. People with a <em>“fixed mindset”</em> are terrified of making a mistake. They worry that when others notice that, then they will lose their top-level position. They focus on not <a href="https://www.youtube.com/watch?v=ZQUxL4Jm1Lo">being exposed as a fraud</a>. So they will avoid the risk at all cost. They won’t take the opportunity to go beyond the zone where they feel confident because it may appear that they’re not good enough. It may turn out that they have no talent and “all is lost”.</p> <p>This approach is popular in enterprise corporations. There is no reward for good, just punishment for bad. The only sure strategy is to do nothing. Then you are minimising the chance of doing something wrong and getting a penalty.</p> <p>I also recently read an article <a href="https://medium.com/swlh/why-senior-engineers-hate-coding-interviews-d583d2855757">“Why Senior Engineers Hate Coding Interviews”</a>. The author writes why <em>“seniors”</em> do not like to do technical exercises. I would even further and say that they are not keen to answer questions about the technical details.</p> <p>In my blog post, which I blatantly called <a href="/en/architect_manifesto/">“Architect manifesto”</a>, I stated that architects should be active programmers. They should be working on the architecture they designed. Architects should get their hands dirty with the code along with the team.</p> <p>Quite often, our projects’ <em>“organisational culture”</em> (or, in fact, the lack of it) requires frequent meetings, e-mails, Excel spreadsheets. We’re doing Meeting-Driven-Development and E-Mail Oriented Programming. Quite often, programmers fall into this vicious circle and don’t have time for coding, especially experienced ones.</p> <p><strong>Nevertheless, is this the only reason why senior programmers and architects don’t want to code?</strong> Do they really have such essential tasks always that they can’t sit down and write a few lines of code? Why is adding a form in the UI such a big detraction? Why is it such a problem to add the CRUD endpoint or table? Why do they not want to <em>“waste their time on such simple things”</em>?</p> <p>I think that each of us had the moment when we thought about getting hands-on helping to deliver some feature. Then we realised that <strong>“I have no idea how to style this component”</strong> or <strong>“How the hell do I do validation in this endpoint?!”</strong>. Then many of us were afraid to ask others and admit that we don’t know something. How do we react in this situation? Are we <em>“fixed”</em> and defend our base by taking on <em>“another urgent task”</em>? Or are we focused on growth and ask a junior for help with CSS?</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[How to create a custom GitHub Action?]]>https://event-driven.io/en/how_to_create_a_custom_github_action/https://event-driven.io/en/how_to_create_a_custom_github_action/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ec95efcda89d4725162c71dce0c6afd8/d2429/2020-05-12-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACAElEQVQoz2VR32vTUBTOPyRuIHsQrLOyB8UfII5tD77sQRApijAGe/DBF8WBHQ76ohN907mNvRQcJDStDUkbmy2Y2zY0bVpjs4R01ZuYLHG5vdJGSpzn4XLvud93zvm+QwwGA/xfRMnDQwNCGGU0TQMA+L4f/UZBmKY5RkeBEMIYg2o1cSk5O7+wvbNlmgbP8wzD9Pv9OJjgOC7+ji7Hnjs7Nzc1de7m1ZmzkxPnExefr64qitJut3VdhxC2Wi3btolCoRAEwam2/JfKmcmJ9Scr3wvZzRePFxdulcqlbrfLMEyxWBQEIZvNqqpKSJIUlzHA+LfvcflPyeQ0u/XG3v+MxL0GtXsSnoRhqGnfVFU1DKPZbLIsS/A8HyMP2/qWomwvpdderq+kau8zXzczQODRYAiKm5rL5QgAgOu6CKEgCBzHwRjv5+nlB/eePkotziQ+vkp3Nd355XrHXiQKIRSGIca4VqsNx6ZpmmVZmqbz+Xyv13u9sXH3furK5ek7N67vfnjXIdN9vfmPtNEIsiwThmEIgtBoNOr1erlcBgD0j47Eg4OHS8vXbs+/zTyrrl2w9fqIjOIbEUWRGJfEGFuWRZJkpVLxPM+yLIokbfgT/uiNCY7jRBohhJIkEX+NGm3Idd1SqSTL8hg9smh4+EFgmibHcYIgaJpGUVSn0/kDfiYykH72fqwAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ec95efcda89d4725162c71dce0c6afd8/a331c/2020-05-12-cover.png" srcset="/static/ec95efcda89d4725162c71dce0c6afd8/36ca5/2020-05-12-cover.png 200w, /static/ec95efcda89d4725162c71dce0c6afd8/a3397/2020-05-12-cover.png 400w, /static/ec95efcda89d4725162c71dce0c6afd8/a331c/2020-05-12-cover.png 800w, /static/ec95efcda89d4725162c71dce0c6afd8/d2429/2020-05-12-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>As you may know, I’m a fan of documentation. I wrote a few practical tips on this topic on the blog:</p> <ul> <li><a href="/en/how_to_create_projections_of_events_for_nested_object_structures/">“How to create projections of events for nested object structures?”</a>,</li> <li><a href="https://event-driven.io/en/architect_manifesto/">“Architect Manifesto”</a>,</li> <li><a href="/en/how_to_configure_algolia_for_your_site_search/">“How to enhance and configure your site search with Algolia?”</a>.</li> </ul> <p><strong>I believe that high-grade documentation lives and evolves with the project.</strong> Documentation makes sense when it reflects the current state of the code. There is nothing worse than outdated documentation; If we have to look at the code anyway, it’s a waste of time.</p> <p>Today I would like to show you a little bit of our kitchen. It would be an understatement to say that the EventStoreDB documentation wasn’t the best. With painstaking work, we have slowly reached a place where you can say that the documentation is “OK”. EventStoreDB is a large, complex project. There are many use cases to consider for each change. Until now, the documentation was created after the functionality was added. Programmers were adding a piece of information into the <em>Changelog</em>. Then someone from our DevAdvocacy team was adding docs to that. It wasn’t perfect, because us DevAdvocates were not always able to keep pace.</p> <p>Additionally, we might not know all the details of the implementation without asking questions or analyzing the code. Further, a strong focus on documentation takes our time for other initiatives (e.g. better samples, materials, etc.). Therefore, the step we wanted to do was move the documentation to the code repositories. This allows them to be included in the normal <em>Code Review</em> process.</p> <p>We had already preconfigured scripts for copying documentation from other repositories (see: <a href="https://github.com/EventStore/documentation/blob/master/import/import-client-docs.js">https://github.com/EventStore/documentation/blob/master/import/import-client-docs.js</a>). However, it was not as trivial as it might seem. A lot of things had to be standardized and taken into account. For example, the URL should match the previous one, so that:</p> <ul> <li>SEO was correct.</li> <li>old URLs were opening.</li> <li>Internal linking worked.</li> </ul> <p><strong>Documentation for different versions of EventStoreDB varies.</strong> If we wanted the documentation to be where the code was, it still had to be placed on the appropriate release branches. This creates some difficulties because our documentation (despite significant improvements) still has gaps. We do not yet have the comfort of documenting only the latest version, as we often document things that touch upon several versions.</p> <p><strong>I think that with mechanical, repetitive work, it is easy to make stupid mistakes.</strong> We would need to remember when cherry-picking between several branches, and issuing separate pull requests etc. I am irritated when someone says <em>“it’s easy, a programmer should remember this”</em>. Maybe it’s simple, perhaps one could remember it, but what for? If we sum up such cases where we have to remember something, it turns out that we do nothing else at work other than going through a checklist in our head. Boring, repetitive situations have to be automated.</p> <p><strong>That’s why we decided to implement automation.</strong> Last week, I added a custom GitHub action, which will automatically do a <em>cherry-pick</em> of changes from a merged pull request. For each <em>cherry-pick: {target_branch}</em> label on the pull request, it will create a separate PR with a copy of the changes.</p> <p><strong>GitHub allows you to do your own actions</strong> using Docker or JavaScript. I used the latter approach. The action code itself is relatively simple, but for how much swearing and nerves it cost me… that’s a secret! GitHub actions enable a lot of scenarios, but they have terrible documentation. Hence my idea to share this example with you.</p> <p>We can define our own action in the repository where the code is located. We can also create a dedicated repository that will store shared actions. We chose the latter solution because:</p> <ul> <li>It’s easier to maintain.</li> <li>Changes put there will be immediately visible the next time you run the action in another repository. No additional changes needed, etc.</li> <li>You can have a code shared between different actions.</li> </ul> <p><strong>What problems did I encounter besides the poor GitHub documentation?</strong> For example, if we do an action using JavaScript, we have to keep <em>node_modules</em> in the repository. Currently, GitHub will not install the dependencies itself when starting the pipeline. This makes sense because we have the known dependencies on every call. Nevertheless, it is most of all a pain in the ass. As you know, <em>node_modules</em> can take up a lot of space. We have to be strict about their weight. Additionally, our .gitignore rules can play a joke on us. For example, I lost a lot of time when it turned out that the rule ignored files from one package. One of the libraries I used had a dependency on the NPM package named <em>debug</em>. That was irritating.</p> <p>GitHub Action code is available here:</p> <ul> <li>README - <a href="https://github.com/EventStore/Automations/tree/master/cherry-pick-pr-for-label">https://github.com/EventStore/Automations/tree/master/cherry-pick-pr-for-label</a></li> <li>code - <a href="https://github.com/EventStore/Automations/blob/master/cherry-pick-pr-for-label/index.js">https://github.com/EventStore/Automations/blob/master/cherry-pick-pr-for-label/index.js</a></li> <li>shared code - <a href="https://github.com/EventStore/Automations/tree/master/lib">https://github.com/EventStore/Automations/tree/master/lib</a></li> </ul> <p>As you can see, the code is relatively simple. It uses an SDK shared from GitHub called Octokit (<a href="https://docs.github.com/en/rest/overview/libraries">https://docs.github.com/en/rest/overview/libraries</a>). I am pretty happy with this action because apart from the logic, it can also comment and inform about the status. It makes the process way easier. For a sample comment, see here: <a href="https://github.com/EventStore/EventStore/pull/2903#pullrequestreview-633505402">https://github.com/EventStore/EventStore/pull/2903#pullrequestreview-633505402</a>.</p> <p><strong>How to use this action?</strong></p> <p>Just define the workflow in the repository (<a href="https://github.com/EventStore/EventStore/blob/master/.github/workflows/cherry-pick-pr-for-label.yml">https://github.com/EventStore/EventStore/blob/master/.github/workflows/cherry-pick-pr-for-label.yml</a>):</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> Cherry pick PR commits for label <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token comment"># you can use pull_request, but...</span> <span class="token comment"># pull_request_target runs with the approver permission</span> <span class="token comment"># which is critical to enable also for forks</span> <span class="token key atrule">pull_request_target</span><span class="token punctuation">:</span> <span class="token key atrule">types</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>closed<span class="token punctuation">]</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">cherry_pick_pr_for_label</span><span class="token punctuation">:</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Cherry pick PR commits for label <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v4 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Cherry Pick PR for label <span class="token comment"># you setup here the separate repository</span> <span class="token comment"># where action is located:</span> <span class="token comment"># - repository: EventStore/Automations</span> <span class="token comment"># - relative path: /cherry-pick-pr-for-label</span> <span class="token comment"># - branch: @master</span> <span class="token key atrule">uses</span><span class="token punctuation">:</span> EventStore/Automations/cherry<span class="token punctuation">-</span>pick<span class="token punctuation">-</span>pr<span class="token punctuation">-</span>for<span class="token punctuation">-</span>label@master <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">GITHUB_TOKEN</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.GITHUB_TOKEN <span class="token punctuation">}</span><span class="token punctuation">}</span></code></pre></div> <p>Interestingly. I did not find it anywhere in the documentation…</p> <p>What’s even more interesting: you can use this action in your repository. For example, to sync code across branches. Of course, long-lived code branches are a pain. However, we all know that sometimes you cannot do without them.</p> <p>Your own GitHub actions give you a lot of possibilities. You can create very interesting automations. You have to work through these first problems. I hope this article and these examples will make it easier for you, and I encourage you to play with this code. Let me know what potential uses you see yourself.</p> <p>Read also other articles around DevOps process:</p> <ul> <li><a href="/en/configure_ci_for_integration_tests/">A simple way to configure integration tests pipeline</a></li> <li><a href="/en/how_to_buid_an_optimal_docker_image_for_your_application/">How to build an optimal Docker image for your application?</a></li> <li><a href="/en/marten_and_docker/">How to create a Docker image for the Marten application</a></li> <li><a href="/en/docker_compose_profiles/">Docker Compose Profile, one the most useful and underrated features</a></li> <li><a href="/en/tricks_on_how_to_set_up_related_docker_images/">A few tricks on how to set up related Docker images with docker-compose</a></li> <li><a href="/en/custom_test_container_on_esdb_example/">How to configure a custom Test Container on the EventStoreDB example</a></li> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions/">How to build and push Docker image with GitHub actions?</a></li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[Memoization, a useful pattern for quick optimization]]>https://event-driven.io/en/memoization_a_useful_pattern_for_quick_optimisation/https://event-driven.io/en/memoization_a_useful_pattern_for_quick_optimisation/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f24a359da259301ca1c1885c56f5631c/d2429/2020-05-05-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AAECAQMFBAcLCh8vJSQ2KwkQCAsSETBYUR01LR4OAjYyJC5KRQ4VFA4aFy5KPSItJCIuIQ8YEwIDAwECAQADBAQCBgQAAQALGxYWKR4xTEZpmpRiZ1XAt6K/q5Whdl5KS0OGwb80UU0kOi4tPTMMEwwFCQcECAcAAQEAAQIBDBMQLj84IS0iDSkho+rpdZuTilo99Mms/e/c6bigWikZWHl4tff3JEQ+IzguJTkyBQwKBAcGAwUEAAIDAyI2LeD37tLRzC9eVITPyk9iXK+AYeO2m/HIssuWgZVcSDVAPZDQzi9eVKmuq+v28RYpIQIEAwQIBgANExAMHRZqdG6AgH0NEQwnLCQeIRzLp4v/6dHerpjjppHZmoAeGBQaIBwYIxpVWlVzdHEWHRUJDgoDBQUAAgUECRANAgsHBhYQBxIPBg8NAwECnV5EyYltmoJ5l15Nlk84CAkJCxsZCRAOCRIQAAAABAgHAgQEBgwJAAYIBQwaFA4WESZEPQcKCg8/ORhkXWpHNsiNc5+JgLB3ZGQvISBrXzeThAcNDSA5NQgLCQ4TDQYIBgQHBQAdIh8mNjIwPjcmS0YDDg0DBQQMTUdCOi3doYD+xq3Qh3IwFQ8OPzgMIR0ACgokWFIXGBYWFxQUFxUQFBAAFR4bHi8rFyYlBxgYED46EhELAAAASSYZpV0/UiwfSiAVOhYQCAYDCwQBEzUwDCEfEx8eFS0qDhUTCw0MAAAHBg8lHzY/MTY6Kh9DNxYXECw0KLOJbtyKaqFYRLKEcLOnkUlOPhwkGDdCMGNmSmBuVS1FOAULCQAAAAALDAlQUDdkX0FsaUpSVDtRWEJtelrDzrb17eC/nIfTzbLg6NN9jG1jdVlqd1pseF5udVl5gWIuMyYAAABqd60RK5HSiAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/f24a359da259301ca1c1885c56f5631c/a331c/2020-05-05-cover.png" srcset="/static/f24a359da259301ca1c1885c56f5631c/36ca5/2020-05-05-cover.png 200w, /static/f24a359da259301ca1c1885c56f5631c/a3397/2020-05-05-cover.png 400w, /static/f24a359da259301ca1c1885c56f5631c/a331c/2020-05-05-cover.png 800w, /static/f24a359da259301ca1c1885c56f5631c/d2429/2020-05-05-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Today I would like to show you a simple programming pattern that can be useful for quick code optimization. This pattern is called Memoization. It comes from the functional language and is used to remember the result of the function. The main idea behind it is to execute function only once. Follow up calls should not run the logic but return the cached result.</p> <p>The example will be in C#, but it should be translatable into other prefered programming language.</p> <p>We will create a static class that has the <a href="https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods">“extension method”</a> <em>Memoize</em>. Thanks to that, we will be able to call it more conveniently for the function we choose.</p> <p>The Memoize method has a single input parameter: the function that will run the business logic. The result of Memoize method is also a function. As described above, the idea is that we pass a function that does some sort of computation and returns it wrapped with a cache logic.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Memoizer</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Func<span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Memoize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span> func<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// create cache ("memo")</span> <span class="token class-name"><span class="token keyword">var</span></span> memo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">Dictionary<span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// wrap provided function with cache handling</span> <span class="token keyword">return</span> input <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// check if result for set input was already cached</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>memo<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span>input<span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token class-name"><span class="token keyword">var</span></span> fromMemo<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// if yes, return value</span> <span class="token keyword">return</span> fromMemo<span class="token punctuation">;</span> <span class="token comment">// if no, call function</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token function">func</span><span class="token punctuation">(</span>input<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// cache the result</span> memo<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>input<span class="token punctuation">,</span> result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// return it</span> <span class="token keyword">return</span> result<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>We’re using function scope (<em>closure</em>) here. We define a dictionary (variable <em>memo</em>) in which we will remember the results of the function.</p> <p>Next, we generate a wrapping method that will check if there is already a cached result for the given input parameter. If it is, it returns the result from the cache and does not call the function itself. If not, it calls the function, adds the result to the cache, and returns it.</p> <p>What is important is that the function that we are going to “memoize” should be deterministic and not cause side effects. What does it mean in practice? This means that it will always return the same result for the given input parameter and will not make any changes. For example, for the same postal code, we will always get the same city. A given insurance number corresponds to a specific person, and so on. We also call this method a “Higher Order Function”.</p> <p>A more real-life example can be, e.g. slow but deterministic operations such as the reflection mechanism. For example, memoization could be useful to check whether a given type has a specific attribute (annotation).</p> <p>Let’s define a method to verify whether a given type has a given attribute as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name">Func<span class="token punctuation">&lt;</span>Type<span class="token punctuation">,</span> Type<span class="token punctuation">,</span> <span class="token keyword">bool</span><span class="token punctuation">></span></span> hasAttribute <span class="token operator">=</span> <span class="token punctuation">(</span>type<span class="token punctuation">,</span> attributeType<span class="token punctuation">)</span> <span class="token operator">=></span> type<span class="token punctuation">.</span><span class="token function">GetCustomAttributes</span><span class="token punctuation">(</span>attributeType<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">Any</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Unfortunately, we cannot memoize this method in its present form because our Memoize method assumes that the function will have one input parameter. The above has two.</p> <p>We need to curry this function. How can this be done? The feature of higher-order functions is that they can be composed. For example, as follows:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name">Func<span class="token punctuation">&lt;</span>Type<span class="token punctuation">,</span> <span class="token keyword">bool</span><span class="token punctuation">></span></span> hasSomeCustomAttribute <span class="token operator">=</span> type <span class="token operator">=></span> <span class="token function">hasAttribute</span><span class="token punctuation">(</span>type<span class="token punctuation">,</span> <span class="token keyword">typeof</span><span class="token punctuation">(</span><span class="token type-expression class-name">SomeCustomAttribute</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>We create an additional function that takes a specific parameter as the type of attribute - corresponding to the type of attribute we choose.</p> <p>We can memoize this function by:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name">Func<span class="token punctuation">&lt;</span>Type<span class="token punctuation">,</span> <span class="token keyword">bool</span><span class="token punctuation">></span></span> hasSomeCustomAttributeMemo <span class="token operator">=</span> hasSomeCustomAttribute<span class="token punctuation">.</span><span class="token function">Memoize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>If we use it several times now, thanks to the memoization for the given type of the attribute, the function <em>hasAttribute</em> will be called only once.</p> <p>Of course, our implementation is quite naive, e.g. it’s not thread-safe. We could enhance and simplify that by using <a href="https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/how-to-add-and-remove-items">ConcurrentDictionary</a> class:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Func<span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Memoize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span> func<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// create cache ("memo")</span> <span class="token class-name"><span class="token keyword">var</span></span> memo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ConcurrentDictionary<span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// wrap provided function with cache handling</span> <span class="token comment">// get a value from cache if it exists</span> <span class="token comment">// if not, call factory method</span> <span class="token comment">// ConcurrentDictionary will handle that internally</span> <span class="token keyword">return</span> input <span class="token operator">=></span> memo<span class="token punctuation">.</span><span class="token function">GetOrAdd</span><span class="token punctuation">(</span>input<span class="token punctuation">,</span> func<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>This version is still not perfect. When we are memoizing many items, our cache may grow exponentially and generate a memory usage issue. Generally, we’d like to keep in memory only the actively accessed entries. Not accessed, we can evict. We’d like to be able to set up a top limit of entries in our cache. To do that, we could use, e.g. <a href="https://redis.io/">Redis</a> instead. If we need a simpler/lightweight solution, we can choose a <a href="https://docs.microsoft.com/en-us/aspnet/core/performance/caching/memory?view=aspnetcore-5.0">MemoryCache</a> class. A sample implementation can look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">Func<span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span> <span class="token generic-method"><span class="token function">Memoize</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">this</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span>TInput<span class="token punctuation">,</span> TResult<span class="token punctuation">></span></span> func<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// create cache ("memo")</span> <span class="token class-name"><span class="token keyword">var</span></span> memo <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">MemoryCache</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">MemoryCacheOptions</span> <span class="token punctuation">{</span> <span class="token comment">// Set cache size limit.</span> <span class="token comment">// Note: this is not size in bytes,</span> <span class="token comment">// but sum of all entries' sizes.</span> <span class="token comment">// Entry size is declared on adding to cache</span> <span class="token comment">// in the factory method </span> SizeLimit <span class="token operator">=</span> <span class="token number">100</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// wrap provided function with cache handling</span> <span class="token comment">// get a value from cache if it exists</span> <span class="token comment">// if not, call factory method</span> <span class="token comment">// MemCache will handle that internally</span> <span class="token keyword">return</span> input <span class="token operator">=></span> memo<span class="token punctuation">.</span><span class="token function">GetOrCreate</span><span class="token punctuation">(</span>input<span class="token punctuation">,</span> entry <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token comment">// you can set different options like e.g.</span> <span class="token comment">// sliding expiration - time between now and last time</span> <span class="token comment">// and the last time the entry was accessed </span> entry<span class="token punctuation">.</span>SlidingExpiration <span class="token operator">=</span> TimeSpan<span class="token punctuation">.</span><span class="token function">FromSeconds</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// this value is used to calculate total SizeLimit</span> entry<span class="token punctuation">.</span>Size <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">func</span><span class="token punctuation">(</span>input<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Michał motivated me, with the comment below, to even go even further. In functional programming, recursion is a widespread practice. It’s non-trivial as to understand recursion, you need to understand recursion. It can be computation expensive. How to use the Memoization with recursion? Let’s take the Fibonacci sequence as an example. The rule is: the next number is found by adding up the two numbers before it.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token return-type class-name"><span class="token keyword">int</span></span> <span class="token function">Fibonacci</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> n1<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>n1 <span class="token operator">&lt;=</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Fibonacci</span><span class="token punctuation">(</span>n1 <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">Fibonacci</span><span class="token punctuation">(</span>n1 <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>We’ll need to find a way to inject the memoized version of the Fibonacci function. Let’s start with breaking out function into the main and the overload:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token return-type class-name"><span class="token keyword">int</span></span> <span class="token function">Fibonacci</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> n1<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">Fibonacci</span><span class="token punctuation">(</span>n1<span class="token punctuation">,</span> Fibonacci<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">int</span></span> <span class="token function">Fibonacci</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> n1<span class="token punctuation">,</span> <span class="token class-name">Func<span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> fibonacci<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>n1 <span class="token operator">&lt;=</span> <span class="token number">2</span><span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">fibonacci</span><span class="token punctuation">(</span>n1 <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token function">fibonacci</span><span class="token punctuation">(</span>n1 <span class="token operator">-</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>Now instead of the direct self-call, we can inject the function to use while doing recursion. Therefore, we have the possibility to memoize it by doing:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token class-name">Func<span class="token punctuation">&lt;</span><span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token keyword">int</span><span class="token punctuation">></span></span> fibonacci <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">;</span> fibonacci <span class="token operator">=</span> Memoizer<span class="token punctuation">.</span><span class="token function">Memoize</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">int</span></span> n1<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token function">Fibonacci</span><span class="token punctuation">(</span>n1<span class="token punctuation">,</span> fibonacci<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> <span class="token function">fibonacci</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>The trick is that the local <em>fibonacci</em> function is lazily evaluated. That means that effectively it will use the assigned, memoized function while doing the call (doing recursion by that).</p> <p>I know that analyzing recursion can create a headache. It may be more accessible by debugging the <a href="https://github.com/oskardudycz/Memoization/blob/main/Memoization.Tests/RecurrsionWithFunctionTests.cs">test in my sample repo</a>.</p> <h2 id="when-is-it-worth-using-memoization" style="position:relative;"><a href="#when-is-it-worth-using-memoization" aria-label="when is it worth using memoization permalink" class="anchor before"><svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg></a>When is it worth using memoization?</h2> <p>Especially where we have to call the same code many times in one operation. If this code is deterministic, then you can cut a lot of execution time. You can also use it with, e.g. a cache in Redis. When we invalidate it, it will just get us a new value. The basis for optimization is to start with operations that are performed very often. This is simple math:</p> <ul> <li>If we cut 0.1 seconds on an operation performed 1000 times on each call, we will gain 100 seconds.</li> <li>If the operation is performed 10 times and we cut 1 second, we will gain 10 seconds in total.</li> </ul> <p>It is a straightforward technique, but it can bring very tangible results. Additionally, it is an example that functional programming is not so abstract but also practical.</p> <p>You can check the full sample in my GitHub repository: <a href="https://github.com/oskardudycz/Memoization">https://github.com/oskardudycz/Memoization</a>.</p> <p>I hope I helped!</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Per <a href="https://en.wikipedia.org/wiki/Memoization">Wikipedia</a>:</p> <p><em>The term “memoization” was coined by Donald Michie in 1968 and is derived from the Latin word “memorandum” (“to be remembered”), usually truncated as “memo” in American English, and thus carries the meaning of “turning [the results of] a function into something to be remembered”. While “memoization” might be confused with “memorization” (because they are etymological cognates), “memoization” has a specialized meaning in computing.</em></p><![CDATA[How using events helps in a teams' autonomy]]>https://event-driven.io/en/how_using_events_help_in_teams_autonomy/https://event-driven.io/en/how_using_events_help_in_teams_autonomy/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ab4af815bd02d31c469944d7c51db54c/d2429/2020-04-28-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AK3Z67fd7bvg7sLg7sfe68na58DU5LrR5L3V6LrS5K7M4brV57jX67bY7Mbf7sff7bbW67HW7LjZ7bva7AB3yuGL0OKe0+Sl0+Cgz9yf0uCgz92ZzNuXy9mc0uGQ0eOP0uWAzOV/yuaMzOOj0+KKxNh9wNl6vtlvuNkAXbTfacHme8vngc/phtLsiMnclNHjh8/nktjql83PkczTh72/bK67aq68e7vJiMDPdbzYarrXZ7zaY8HeAFGRoUeHnGCam16Ke1iAe0x9aE+VlmatrlqSjkx1WVd2Tj1jRD5eOUVqQ0KGcUp6WGGMbXzE0mKhrVGKoQAyRjFPXkQ4SR84VT9Zh107Sh8kcF5FYydTXhQ+YDYmWTpHXydPZSc6XTM9dU5VeTpKZCxkilVmlWBKlo8AUGNGa3U7PGQ4Mr+3ROrhHbKlKtTOSKN9ZnUpNEggWIpsWHdHW3c+T3AvT4tHWnk9SHhPVW4mTl4UPXRgAEeOdT5RJExWG1B8RTSEZg6xsgO+zA7LzjWdfiOBa0rj2G6xem15M2NyN05ZFktdHEhdJU1dIEBTKElaKwAJi6QXj44mgm8xa0czTiUhcWMVtLkToqQAmKcBoa4Arrgqq50wc1IleGM1lnpOlWlymW99lF5xvJdl0LQABniVAYCcAIadAo6gCImVBISUA4OYC4SRCYOPA4mXBI2ZAYyaAImZAI+fAZOhBKGmRdG7eJRUaqBlU9zGAAJvjANzjgN0jgJ1jQB1jQB3jwB5jwCIlyKonjnFsRbRxAKrqQN9jgNzigJ1iwF1iwCKmDS2nl96NkyCTgABaIYBbIcBcokBfYwNmZ0hqJ8msaYPtKwRrqERsKkJm50Ci5QBd4kCbYQBbYcDb4gDcYkBgpYNkpIZhXXee0OLkGEhxwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ab4af815bd02d31c469944d7c51db54c/a331c/2020-04-28-cover.png" srcset="/static/ab4af815bd02d31c469944d7c51db54c/36ca5/2020-04-28-cover.png 200w, /static/ab4af815bd02d31c469944d7c51db54c/a3397/2020-04-28-cover.png 400w, /static/ab4af815bd02d31c469944d7c51db54c/a331c/2020-04-28-cover.png 800w, /static/ab4af815bd02d31c469944d7c51db54c/d2429/2020-04-28-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Autonomy is the Holy Grail for the management of multiple teams. At least that’s how it’s perceived; it’s not easy to get the right balance. I’ve written in the past on how:</p> <ul> <li>The architect role can impact the team’s autonomy in my <a href="/en/architect_manifesto/">“Architect Manifesto”</a>.</li> <li><a href="/en/sociological_aspects_of_microservices/">“Sociological aspects of Microservices”</a> changes teams relations.</li> </ul> <p><strong>Today, I’m stating that using event-based systems can help in running autonomous teams.</strong></p> <p>The days where we could locate all team members in the same office are gone. Even the most stubborn companies have had to find a way to work remotely. This distributed environment brings benefits and challenges.</p> <p>Let me quote Mel Conway again:</p> <p><em>“Any organization that designs a system (defined broadly) will create a project whose structure is a copy of the organisation’s communication structure.”</em></p> <p>Distributed teams are more likely to create distributed systems. The emergence of microservices from this shouldn’t be surprising. We no longer create applications that are running in a single region, country or even continent. Globalisation, the Butterfly Effect, someone eats a bat one day and a few months later…</p> <p>In terms of communication, it can be both more difficult and more straightforward. On one hand, it’s more complicated because we can’t gather a team in one room to discuss an issue, or discuss a problem in the office kitchen with a hot drink. On the other hand, it’s easier because this type of kitchen discussion can turn into a chit chat without a conclusion. Coffee after coffee, tea after tea, and we still don’t see any result. The knowledge that it’s hard to group people can motivate us to be more precise and on point. We’ll be keener to provide written content like RFC or ADR.</p> <p>The same thing happens with our systems. If we do not define communication rules between services, we’re risking failure. Without defined boundaries, it will go wild. Some API will be created, some requests will be sent. However, we’ll be getting constant random failures or misalignments. We may end up using the <em>“Negotiator pattern”</em>. We have two microservices that don’t talk to each other, so we add a third one in a panic. We hope that the new one will persuade the two others to start talking to each other. You can probably imagine the typical result of those negotiations…</p> <p>Events then come to the rescue. How?</p> <p><strong>Events are concise and carry a specific business message.</strong> We get a clear domain context from the event (via type and data). That helps us to understand what has happened.</p> <p><strong>It is easy to describe business flows with events.</strong> We can do it with the help of <a href="https://www.eventstorming.com/">Event Storming</a>. They enable both microscale and macroscale modelling on how our system should work. What’s more, we can make a precise inquiry. If we send:</p> <ul> <li>correlation id - the identifier of the operation that triggered them, e.g. WebAPI call,</li> <li>causation id - the identifier indicating the order of occurrence.</li> </ul> <p>We will be able to generate a graph of calls. For example, EventStoreDB commercial UI can generate a view like that:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ae2087956abbbcec0393b76b6b0d3a46/37ea5/esdbui.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 49.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAKCAIAAAA7N+mxAAAACXBIWXMAAA7DAAAOwwHHb6hkAAABKUlEQVQoz42QQU/CQBCF+9NVENqi2Ki/yQgVPOjJM4ppoAK7O53dnZk1baGJBBO+wyb78t6b2Y0eX+8e5uPh5CrNr5NpL5n20ryf5L3hcz+eDpM8TvM4m42ylzSbjQZPF+M8vp/f3uSDweQyAsDNRhkF7BkAi6IkT4EDQGFxEbgIUoYgoaEydvH1Xf5s2ddKpEGDRTA1aO3W7BTsKuu8/0R8s/Yd7QcztWGl9GK5XJVrpY3zPtJOrWHlxMmhfs/hJn/lThSRiIUdO0eOhRv1lPcfonNMzIwWj1cLIWJmUxliEpYKK3QoDdZaQBARZm7PE5NFpE42cG3kblqndxUtxERE5659BBE55/bhukwIHSqj6p8L7Nkro9AiCZGQAaNBkxAHrm1ata/4BVYfOC8qy2wSAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="edbui" title="edbui" src="/static/ae2087956abbbcec0393b76b6b0d3a46/a331c/esdbui.png" srcset="/static/ae2087956abbbcec0393b76b6b0d3a46/36ca5/esdbui.png 200w, /static/ae2087956abbbcec0393b76b6b0d3a46/a3397/esdbui.png 400w, /static/ae2087956abbbcec0393b76b6b0d3a46/a331c/esdbui.png 800w, /static/ae2087956abbbcec0393b76b6b0d3a46/8537d/esdbui.png 1200w, /static/ae2087956abbbcec0393b76b6b0d3a46/1a152/esdbui.png 1600w, /static/ae2087956abbbcec0393b76b6b0d3a46/37ea5/esdbui.png 1903w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>It can be used to verify if the actual workflow reflects the business assumptions.</p> <p><strong>Events help to make a business-friendly API.</strong> We can inform the modules directly about what business (domain) events take place. Thanks to that, other teams will understand better what they can expect from us. Additionally, if we <a href="/en/cqrs_facts_and_myths_explained/">use C(Q)RS</a>, we also correlate events with business commands. Having that can show our module as a black box. So, e.g. after calling such a command, you’ll get such an event. The very nature of commands and events makes it easier to view the system in business terms, not technical. Less effort is needed to explain the logic.</p> <p><strong>Events make relations between modules more straightforward.</strong> By using a command and an event, we can show the type of dependency between the modules. For example, we can define that the financial module should wait for the confirmation event from the reservation module to issue an invoice. We’re stating that in the ticket reservation process, the financial module is streamlined to reservation one. If we send a command, then we define strict relation.</p> <p>Thanks to all of that, we can also model the actual relationships between the teams. <strong>It’s an inverted Conway’s law</strong>. By modelling the business process and boundaries for the technical solution, we can get the proper communication rules and team boundaries.</p> <p>Modelling with events helps on keeping the focus on the business flow without getting into nitty-gritty technical details. Using events helps to create a synergy between technology and business solutions. It also facilitates people to start discussions, which itself is attractive, isn’t it?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. check also my <a href="/en/architect_manifesto">Architect Manifesto</a>.</p><![CDATA[How to enhance and configure your site search with Algolia?]]>https://event-driven.io/en/how_to_configure_algolia_for_your_site_search/https://event-driven.io/en/how_to_configure_algolia_for_your_site_search/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e171972f64c5ee96073d481b0a009ef3/d2429/2020-04-21-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AIl8bYx+ZaGNal9RPYOBecedefq/lsaHYH5oVolyWpyHbq2cjc2ejdaVd+mwi+qyjOmthceTb2hdTmJbSgCwopOsqKOqoY+1rJuuqpvDnXf5wZrdmW+ZcD6BbFB8aViUfWifemjYlXnvspLlqYbioXjIpZS3usCVj4sAZVpHZlpHkol2zsu7p6ef48Sv8beP4Jx5t28ksHMIn3c1dWBDmXVc8LGS7KqM662M4J54mXtnp6Wlu7vBAHxybnNmVL+6q4OBdlZGQPHFpeq1k+Wfd8STdqOIX6xyGaRnEM+OcN6lisuaf8qZfcaTc2pTP3NfSod2YwB0cG+yrJ66s550WCCsiWr1yrHms5XYjWjdrJfc4uzCxMWxim3jmn7LlXXBjnKygmyhe2RbRTJiSzZjTDgAdG1e1NHEl5GCq5FproE/7cWp9sCnx4Rny5uHztHU6+/z5t/k3K2cyaKP0aOHwopZo3AximhNe19RcVk8ALy2qaKej4aCfre4wZSBeph2YNWoj9aXgr9xYK2YnqiyvcnO1NvZ2+fe4N3V1cOynLOGQLJ1FqFvKXxcMQDBv7SNhXWtra6yt72xsrWQiIp7aWahgG/Ch09pTyxVU1WEh5CjrLfAxs3i5uny9f3j5u3MvavQmkOMZy0AmpF7tbKxsa6poqGgqKyyrbC2qKaqioSId2hVl39Pn5WDbl1MbWReh4yWmqCrub3D3t/g7vf/0LyhsKKJAKSXg6mglL26uq+po62mn7GwsbGzurCxtpmTmqmiovDt4+ff0auafnBaN3p1b4WJkZSao7q6vb+xmsvLxQDPzcyxq6GPhnayrai8ubq3saq1r6iurrGWlp2GhYqbk5PTz8umnZBtTh10Ux5yWjmIfG6akoa+t6TO0NFU2Iqrf8pJGQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e171972f64c5ee96073d481b0a009ef3/a331c/2020-04-21-cover.png" srcset="/static/e171972f64c5ee96073d481b0a009ef3/36ca5/2020-04-21-cover.png 200w, /static/e171972f64c5ee96073d481b0a009ef3/a3397/2020-04-21-cover.png 400w, /static/e171972f64c5ee96073d481b0a009ef3/a331c/2020-04-21-cover.png 800w, /static/e171972f64c5ee96073d481b0a009ef3/d2429/2020-04-21-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Some time ago, I wrote on how to create documentation without the pain (<a href="/en/how_to_successfully_do_documentation_without_maintenance_burden">read it here</a>). Documentation is something that has been one of the main tasks since I joined Event Store. Tedious, painstaking work, still it has to be done. Year ago, a lot of documentation for EventStoreDB was missing. Now, thanks to collaborative work, it’s much better. We are filling the hole each week. <strong>Recently, we filled a hole deep as the Lake Baikal: content search</strong>.</p> <p>At Event Store, we use <a href="https://vuepress.vuejs.org/">VuePress</a> as the documentation engine. It is a static HTML generator written in Vue. Overall great thing. It looks nice, and it’s extendible - #IRecommendIt. VuePress has a built-in content search engine. Unfortunately, it is elementary - #IDontReccommendIt. We used it for some time because something is better than nothing. However, frankly, documentation without a proper search is a poor one.</p> <p>After a short research, we found that we will use a ready-made tool - <a href="https://www.algolia.com/">Algolia</a>. It is a SaaS solution that allows a website’s content indexing and advanced text search. It also takes care of all the basic stuff like case-insensitive searches, typos correction, phrase searches, etc. The fact that it is a SaaS solution means that you don’t have to build your architecture and worry about operational issues. It just does the work.</p> <p>Of course, it’s not so perfect that you don’t have to do anything. In theory, Algolia has a free autoindexing program. You can sign in, and when you are accepted, Algolia will do scraping and indexing for you. However, it has limited options to adapt to your needs. The other option is to scrap and send data to Algolia on your own. Then we have much more customisation options. How to do it? The easiest way is with a Docker image predefined by Algolia.</p> <p>Before we get to Docker, let’s first define the configuration. We need to set up a JSON file (e.g. <em>config.json</em>). It can look like this:</p> <div class="gatsby-highlight" data-language="json"><pre class="language-json"><code class="language-json"><span class="token punctuation">{</span> <span class="token comment">// name of the index in Algolia</span> <span class="token property">"index_name"</span><span class="token operator">:</span> <span class="token string">"scraper-test"</span><span class="token punctuation">,</span> <span class="token comment">// url of the page to be indexed</span> <span class="token property">"start_urls"</span><span class="token operator">:</span> <span class="token punctuation">[</span><span class="token string">"https://developers.eventstore.com/"</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token comment">// selectors, each lvl is a header</span> <span class="token property">"selectors"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"lvl0"</span><span class="token operator">:</span> <span class="token string">".sidebar h3.version"</span><span class="token punctuation">,</span> <span class="token property">"lvl1"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"selector"</span><span class="token operator">:</span> <span class="token string">".sidebar-heading.open span"</span><span class="token punctuation">,</span> <span class="token property">"global"</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token property">"default_value"</span><span class="token operator">:</span> <span class="token string">"Documentation"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"lvl2"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"selector"</span><span class="token operator">:</span> <span class="token string">".content__default h1"</span><span class="token punctuation">,</span> <span class="token property">"strip_chars"</span><span class="token operator">:</span> <span class="token string">"#"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"lvl3"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"selector"</span><span class="token operator">:</span> <span class="token string">".content__default h2"</span><span class="token punctuation">,</span> <span class="token property">"strip_chars"</span><span class="token operator">:</span> <span class="token string">"#"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"lvl4"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"selector"</span><span class="token operator">:</span> <span class="token string">".content__default h3"</span><span class="token punctuation">,</span> <span class="token property">"strip_chars"</span><span class="token operator">:</span> <span class="token string">"#"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"lvl5"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"selector"</span><span class="token operator">:</span> <span class="token string">".content__default h4"</span><span class="token punctuation">,</span> <span class="token property">"strip_chars"</span><span class="token operator">:</span> <span class="token string">"#"</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// page content selector</span> <span class="token property">"text"</span><span class="token operator">:</span> <span class="token string">".content__default p, .content__default li"</span><span class="token punctuation">,</span> <span class="token comment">// the language of the text</span> <span class="token property">"lang"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"selector"</span><span class="token operator">:</span> <span class="token string">"/html/@lang"</span><span class="token punctuation">,</span> <span class="token property">"type"</span><span class="token operator">:</span> <span class="token string">"xpath"</span><span class="token punctuation">,</span> <span class="token property">"global"</span><span class="token operator">:</span> <span class="token boolean">true</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token property">"custom_settings"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token property">"attributesForFaceting"</span><span class="token operator">:</span> <span class="token punctuation">[</span> <span class="token string">"lang"</span> <span class="token punctuation">]</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Let’s define the _ “. Env” _ file with the necessary environment variables for the scraping process. They are keys and indexes names that we should get from Algolia settings:</p> <div class="gatsby-highlight" data-language="text"><pre class="language-text"><code class="language-text">ALGOLIA_APPLICATION_ID = PASTE_HERE_YOUR_AGLOLIA_APPLICATION_ID ALGOLIA_WRITE_API_KEY = PASTE_HERE_YOUR_ALGOLIA_WRITE_API_KEY ALGOLIA_SITE_URL = https: //developers.eventstore.com/ ALGOLIA_INDEX_NAME = Documentation</code></pre></div> <p>Algolia has several types of API keys to ensure security. We’ll use a separate read-only API Key for search and write for uploading the site data.</p> <p>Let’s define an auxiliary script <em>“scrape.sh”</em>, which we will run for scraping and indexing our site (if you use Windows, you need <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10">Windows Subsystem for Linux</a>):</p> <div class="gatsby-highlight" data-language="bash"><pre class="language-bash"><code class="language-bash"><span class="token keyword">if</span> <span class="token punctuation">[</span> -f .env <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">then</span> <span class="token builtin class-name">export</span> <span class="token variable"><span class="token variable">$(</span><span class="token function">xargs</span> <span class="token operator">&lt;</span> .env<span class="token variable">)</span></span> <span class="token keyword">fi</span> <span class="token function">docker</span> run <span class="token punctuation">\</span> -e <span class="token assign-left variable">APPLICATION_ID</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">printenv</span> ALGOLIA_APPLICATION_ID<span class="token variable">)</span></span> <span class="token punctuation">\</span> -e <span class="token assign-left variable">API_KEY</span><span class="token operator">=</span><span class="token variable"><span class="token variable">$(</span><span class="token function">printenv</span> ALGOLIA_WRITE_API_KEY<span class="token variable">)</span></span> <span class="token punctuation">\</span> -e <span class="token assign-left variable">CONFIG</span><span class="token operator">=</span><span class="token string">"<span class="token variable"><span class="token variable">$(</span><span class="token function">cat</span> config.json <span class="token operator">|</span> jq <span class="token string">'.start_urls=[env.ALGOLIA_SITE_URL]'</span> <span class="token operator">|</span> jq <span class="token string">'.index_name=env.ALGOLIA_INDEX_NAME'</span> <span class="token operator">|</span> jq -r tostring<span class="token variable">)</span></span>"</span> <span class="token punctuation">\</span> algolia/docsearch-scraper:v1.13.0</code></pre></div> <p>We’re running the Docker image prepared by Algolia. Before we do that, we read the environment variables from the _ “. Env”_ file. This image needs the service keys (<em>APPLICATION_ID</em> and <em>API_KEY</em>) to boot. Additionally, we read the content of our configuration file:</p> <ul> <li><em>cat config.json</em>: download content.</li> <li><em>jq ‘.start_urls = [env.ALGOLIA_SITE_URL]’</em>: replace the page URL from config with value from the environment variable.</li> <li> <ul> <li>jq ‘.index_name = env.ALGOLIA_INDEX_NAME’ *: replace the Algolia index name from config with value from the environment variable.</li> </ul> </li> <li><em>jq -r tostring</em> converts JSON to a flattened string.</li> </ul> <p>Why do we need to manipulate the values from <em>config.json</em>? Ultimately, we wouldn’t like to be calling that manually. It would be better if it happens automatically after the website was deployed. How to do it? For example, via GitHub Actions. We can define new workflow - e.g. as <em>algolia-scraper.yml</em>:</p> <div class="gatsby-highlight" data-language="yaml"><pre class="language-yaml"><code class="language-yaml"><span class="token key atrule">name</span><span class="token punctuation">:</span> algolia<span class="token punctuation">-</span>scraper <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token key atrule">push</span><span class="token punctuation">:</span> <span class="token key atrule">branches</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> master <span class="token key atrule">check_suite</span><span class="token punctuation">:</span> <span class="token key atrule">types</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>completed<span class="token punctuation">]</span> <span class="token key atrule">workflow_dispatch</span><span class="token punctuation">:</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">scrape</span><span class="token punctuation">:</span> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> check out code 🛎 <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v4 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> scrape the site 🧽 <span class="token key atrule">env</span><span class="token punctuation">:</span> <span class="token key atrule">ALGOLIA_APPLICATION_ID</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.ALGOLIA_APPLICATION_ID <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">ALGOLIA_WRITE_API_KEY</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.ALGOLIA_WRITE_API_KEY <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">ALGOLIA_SITE_URL</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.ALGOLIA_SITE_URL <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">ALGOLIA_INDEX_NAME</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> secrets.ALGOLIA_INDEX_NAME <span class="token punctuation">}</span><span class="token punctuation">}</span> <span class="token key atrule">run</span><span class="token punctuation">:</span> <span class="token punctuation">|</span><span class="token scalar string"> cd .algolia touch .env ./scrape.sh</span></code></pre></div> <p>Thanks to that, we can replace the Algolia configuration without changing the scraping script code. Tastes? The trigger is run after the merge to the main branch.</p> <p>Why <em>touch .env</em>? For the build, this file will not exist. We have the variable values ​​defined above based on the <em>secrets</em> of the repository. We should never put these API keys in a Git repository. <em>.Env</em> file for the convenience of local work.</p> <p>If you want to see the PR where I implemented it, check the documentation repo: <a href="https://github.com/EventStore/documentation/pull/306">https://github.com/EventStore/documentation/pull/306</a>.</p> <p>You can check the search results at <a href="https://developers.eventstore.com/">https://developers.eventstore.com/</a>. Yes, I know the popup look&#x26;feel is questionable, but it’s <em>“good enough”</em>.</p> <p>If you want to play with Algolia, it also has a free version (up to 10,000 searches per month).</p> <p>I hope you found my post interesting. Besides the Algolia configuration, I wanted to show you that sometimes it is worth using ready-made things instead of creating everything on your own. I showed the potential decision-making process, plus a complete example of how to automate work with tools. <strong>You can take it from there and use it for your documentation, blog or even application data.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If GitHub actions look interesting to you, read also other articles on this topic:</p> <ul> <li><a href="/en/how_to_buid_and_push_docker_image_with_github_actions">How to build and push Docker image with GitHub actions?</a></li> <li><a href="/en/how_to_create_a_custom_github_action/">How to create a custom GitHub Action?</a></li> </ul><![CDATA[Events should be as small as possible, right?]]>https://event-driven.io/en/events_should_be_as_small_as_possible/https://event-driven.io/en/events_should_be_as_small_as_possible/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2065f4f8cb1572fcd7546a1299d0db3d/d2429/2020-04-14-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACjklEQVQoz2No8jHrC3O4vnvN/3+///0HgX//IDSU/fP7jwMbNxUEBexbvTTPVjfDQr091HFtpt+iCFuGqbGOc2Nt2j11VnZWv3v++O/fv/8xwJ+/f188uL+5pLBQSWqqhdYkM5XZZqoztWQZJiW4pjobKgrzGohwr57Y9v///0c3rvz59RNu8z+wcX///9/q7brKQK7HWKHHSmtnjt+SSDuGMAMlcW42BgYGEVaGk/t2vn/96tLxo+iO//P7////Nxd0L5HnbLVWyfb3nJ8UvDjKjmFJQWhDuLOvnnxWVODDy6cfXDlz6+LZr69ffPvw/tevX7+/f//z9cvff//Onz55Y8PC1WYSaxI9liT7bs0M3F0Zw3BgUun6moS9czo+vX/19/uX39+/Pr195eSy2c8unP7///+d/TsfnT3x5cePue0NL7av3hugf6E+9Uxn1pXpNTfmNjNMSXTvy4y4cfbE3p1b169e8vr5o/cvn/RVF2XHhZ88efzM0YMnDh94dvTw4fqcq1Obzub6n4i3vD6z4vqUkgs9WQwhptrtRRm9jdWaKrLuLtb3bl598eBOepgvPzggJkzu///p49XJTWfqMs9UZVyoTz9fFXm82OdUXeTxukiGVG8HLzM9EzV5SwONrsaq80f23b92aWpDuYaCmH+A9737d////nmgPnNelOfx0oT1ASbXJ1Reakk9URu9vzSEIdlEOUJbqsDLvDs9dNPE+i2T6g9Nqj+3a92DW9d/gwP5////X9++PjZv8tokrwOF0XenNl5qyz5WH7+zMJBhV6nnkZaIA61x87L98r3MXHXlNzRXgqLn//+/f/6A0gw4zn58eHtmQu3lyY1X+ivONqUerInenOcLAEMhn0kamTz1AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/2065f4f8cb1572fcd7546a1299d0db3d/a331c/2020-04-14-cover.png" srcset="/static/2065f4f8cb1572fcd7546a1299d0db3d/36ca5/2020-04-14-cover.png 200w, /static/2065f4f8cb1572fcd7546a1299d0db3d/a3397/2020-04-14-cover.png 400w, /static/2065f4f8cb1572fcd7546a1299d0db3d/a331c/2020-04-14-cover.png 800w, /static/2065f4f8cb1572fcd7546a1299d0db3d/d2429/2020-04-14-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>TV size? The bigger, the better. Debt amount? Opposite. It’s hard to find the right size that suits all. How big should the event be? What amount of information should it contain? Unfortunately, we haven’t managed to standardise the SI unit on that yet. In this post, I’ll discuss basic rules on that topic.</p> <p><strong>The most common statement is that the event should be as small as possible</strong>. It is roughly accurate. What does “as small as possible” mean? The answer is not apparent. Let’s think about the reason for publishing events. An event is information about a fact in the past (<em>Read more about event basics in my other article <a href="https://event-driven.io/en/whats_the_difference_between_event_and_command/">“What’s the difference between a command and an event?”</a></em>).</p> <p>It is an inverted type of communication. In the classic HTTP API, the interested client must request the service. By publishing the event, we inform all listening modules of its occurrence. We might not even know if anyone is interested. We’re unsure and don’t know what will happen after the message is received. That’s okay most of the time, as it allows for decoupling of services and setting correct boundaries.</p> <p>Contract definition practices and review are pretty standard for Web API. There’s a lot of discussion about whether or not we’re designing a system according to REST practices. For some reason, such an approach is not typical for the events’ definition.</p> <p><strong>In my opinion, Web API and events’ design are not so different. Both of them should be treated as the public API.</strong> Of course, they have other formats, protocols, etc.; however, general design principles are the same. If we’re using an <em>API-first</em> approach, we should define public API as the first step of our design process. By public, I mean <em>“public-public”</em> available for all and an <em>“internal public”</em> API between our services. The API definition should be our starting point for the system design.</p> <p><strong>The contrary approach is <em>“Backend for frontend”</em></strong>, where API is tailored for the client applications’ needs. In this approach, the client’s preferences are the most important. The client application should be able to use endpoints as effectively as possible.</p> <p>Both approaches have advantages and disadvantages. <em>“API first”</em> is usually more consistent, more organic. Nevertheless, it can cause difficulties for the clients, as they need to adapt and sometimes do workarounds. <em>“Backend for frontend”</em> allows clients to work more efficiently. However, it moves more effort on the backend. The duplication is more significant, and it may be harder to maintain a consistent vision.</p> <p><strong>Why am I writing about Web API when I should write about events?</strong> By creating an event-based system, we will not avoid these dilemmas. Let’s take the invoicing process as an example. After the final confirmation of the order:</p> <ul> <li>The Financial module should issue an invoice.</li> <li>The Shipment module should send it.</li> <li>The Notification module should send an e-mail.</li> </ul> <p>Accordingly, we can define an <em>OrderConfirmed</em> event with all the information collected during the process, e.g. the buyer’s data, address, total amount, and order details. However, it may turn out that the shipment module does not need detailed financial data. It only needs to know where and to whom to send the product. The financial module does not need address data for shipment, but only the company data (which may differ from personal). The notification module, in turn, should not know anything about the buyer except his name and e-mail. The only thing that we’ll be sending in an e-mail is a link to the order page.</p> <p>Therefore, it may turn out that the OrderConfirmed event in such an amassed form will have redundant data. <a href="/en/gdpr_for_busy_developers">Adding GDPR into the equation makes things more challenging</a>. We might not want to send all data everywhere. Therefore, instead of one event, you can publish three:</p> <ul> <li><em>OrderConfirmed</em> with necessary order data.</li> <li><em>OrderReadyForShipment</em> with data for the shipment module (like address, etc.).</li> <li><em>OrderPaid</em> with financial information.</li> </ul> <p>Thanks to this, each module will be listening to a specific event. Those events can be sent to different stream/queue/topic/subscription. As with <em>“Backend for frontend”</em>, this can cause duplication of data and a slightly higher maintenance cost. However, it can be a much better solution than one event to rule them all. We’re also risking bigger coupling between services. We need to know what other modules need, and our module must adapt.</p> <p>On the other hand, <strong>a common mistake is taking the rule that events should be as granular as possible literally</strong>. Let’s go back to our invoicing example. Before we place an order lot of things may happen:</p> <ul> <li>User shopping can be initiated.</li> <li>Product may be added to cart.</li> <li>Deliver address may be selected.</li> <li>Product availability may be confirmed or denied.</li> <li>etc.</li> </ul> <p>All of those events are relevant and meaningful for the ordering module. We want to gather as much business information as we can. It’s perfectly fine to have them as granular as possible. However, if we’re going to publish all of them outside, that could be a huge issue. By doing that, we’re asking other modules to:</p> <ul> <li>get user data from <em>BucketAssignedToUser</em>.</li> <li>product data from <em>ProductAddedToBucket</em>.</li> <li>address data from <em>DeliveryAddressSelected</em>.</li> </ul> <p>In short, we’re demanding other modules know all the internal details of our process. <strong>It is the first step to a distributed monolith.</strong></p> <p>What if we extend the process by an additional event? What if we change the shape of events? For example, if the financial module does not know that we added the <em>ProductQuantityUpdated</em> event, it might not be able to not generate the correct data for the invoice.</p> <p><strong>It gets demanding not only for others but also for us.</strong> We can ignore other’s needs and provide breaking changes. However, if we care for our product’s success, then we need to develop coordination. Inform others about breaking changes, etc.</p> <p><strong>I suggest splitting events into Internal and External.</strong> Internal are meaningful in the specific module context. External are understandable in the context of the entire system and overall business process.</p> <p>Can an event be internal and external at the same time? Of course they can, even the previously mentioned <em>OrderConfirmed</em>. However, if we have five events that change the order status, it might not be convenient to pass them externally. If other modules are only interested in information about the status change, we can do an event mapping. We can create an internal Event Handler that will listen for internal events, then map to the external <em>OrderStatusChanged</em> event and publish it outside. In EventStoreDB, you can use <a href="https://developers.eventstore.com/server/v21.2/docs/projections/#introduction-to-projections">projections</a> for events transformations and <a href="https://developers.eventstore.com/clients/grpc/subscribing-to-streams/#subscription-basics">subscriptions</a> for listening to the projected stream and forwarding it further.</p> <p>So there is no best answer. As usual: it depends.</p> <p><strong>Therefore, our events should be as small as possible, but not smaller.</strong> When designing them, let’s keep a healthy pragmatism and not forget that they’re also an essential part of our public contract.</p> <p><strong>Read also the extended follow up to this article <a href="/en/internal_external_events">Internal and private events, or how to design event-driven API</a>.</strong></p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. Check my two other articles where I expand more on the events in different contexts:</p> <ul> <li><a href="https://event-driven.io/en/how_to_create_projections_of_events_for_nested_object_structures/">How to create projections of events for nested object structures?</a>.</li> <li><a href="https://event-driven.io/en/how_to_do_event_versioning/">How to (not) do the events versioning?</a>.</li> <li><a href="https://event-driven.io/en/bank_account_event_sourcing/">Why a bank account is not the best example of Event Sourcing?</a></li> </ul><![CDATA[How to create projections of events for nested object structures?]]>https://event-driven.io/en/how_to_create_projections_of_events_for_nested_object_structures/https://event-driven.io/en/how_to_create_projections_of_events_for_nested_object_structures/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8041984793e3ff13377a1d32fe1aa537/d2429/2020-04-07-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABvUlEQVQoz2P4jwf8+welweSRKf2rWsuR5Rmwafn37+/ff//+/v///+2t87d2Lvjz98+LKR2TdUXT7A1/fngHUYOu+d+/v//+/gVZCJH88eNIQ8zKJPUnB9b3hno0ORkdbk++vmMGRCWmzSAtv759/PzsDojx4v7uwsC9FQ7vTq47NrNrVmbCgf6YU6t74D5C0fzl/tlXeyYd7yvsDbF7srXj17UNl9fN3tud+3TvwlV5kdNTol4+evT7529YIEA1gy38+Oz6/JLH6zp2FCTWOBqd6I/4sK3lzeFZPz69vb9nWaWbRZGd2ZWDB/7////3zx/0APv59PKXKxs/nFn2emfr02Upt+fEn+wOO9kR+fPdi093L53qiZlbnPPq7h14aME0gzl/nzx4Orv9+pTSW72pOxtiN1SFTIqx3t5Z9f/bt7+PD56pDd/QXIUcfwibIYbdO7RrcWrQTh/nKieXigDPDFe3/Qtnfrh95cS6+Tl+TmsndaBFKgNyevj3+2dnaa6htIKGqJSKqGiUveXWKZ2PLp5+//rly+dPf//8gUMzzPLP797u3rRu9uS+1QtmXT1x+OPrl//+/sOVAgF+5SH25F+YCQAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/8041984793e3ff13377a1d32fe1aa537/a331c/2020-04-07-cover.png" srcset="/static/8041984793e3ff13377a1d32fe1aa537/36ca5/2020-04-07-cover.png 200w, /static/8041984793e3ff13377a1d32fe1aa537/a3397/2020-04-07-cover.png 400w, /static/8041984793e3ff13377a1d32fe1aa537/a331c/2020-04-07-cover.png 800w, /static/8041984793e3ff13377a1d32fe1aa537/d2429/2020-04-07-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Did you ever feel so encouraged so much that you immediately thought, “This is great, let’s do it!“. I’m sure you did. I’m also sure that few times you got backslash immediately after facing the more advanced scenario. I felt like that with Event Sourcing. I quickly realised that’s the way to go, but I found that I don’t know what I’m doing when I started coding.</p> <p>Events projections may get tricky. Let’s discuss today one of the non-trivial cases. As a reminder, projection is the interpretation of a certain set of events. It is usually used to build a read model. For example, cinema ticket reservation may include the following events <em>Tentative Reservation Created</em>, <em>Reservation Seat Changed</em>, <em>Reserved Confirmed</em>. By applying them one by one, we know if the reservation was confirmed and what was the final seat number. We can save this result as an entry in the database (e.g. relational). We can also produce an event and save it to a separate stream. Then the last event in this stream represents the current read state of the model.</p> <p>It does not seem particularly difficult, and it does not have to be. However, when we go deeper into that, it may turn out that it is not always that simple. <strong>What if we have relationships between different projections results?</strong> For example, if we have a School with Students and their parents? How to create a projection in which we have the school and all the children with their parents’ names? How to handle nested relations?</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">SchoolDashboard</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> SchoolId<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">;</span> <span class="token class-name">StudentWithParents<span class="token punctuation">[</span><span class="token punctuation">]</span></span> Students<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">StudentWithParents</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">;</span> <span class="token class-name">Parent</span> Mother<span class="token punctuation">;</span> <span class="token class-name">Parent</span> Father<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">Parent</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>In my opinion, there are three main options for approaching this topic. Plus grey area:</p> <ol> <li><strong>Publish a bigger event</strong> (<em>“fat event”</em>), which has all the data needed to build a read model by projection. For example, <em>StudentAddedToSchool</em> with <em>StudentId</em>, <em>SchoolId</em>, but also whole student data together with nested parent data. <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">StudentAddedToSchool</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> SchoolId<span class="token punctuation">;</span> <span class="token class-name">StudentWithParents</span> Student<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">SchoolDashboardProjection</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Projection<span class="token punctuation">&lt;</span>SchoolDashboard<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token function">SchoolDashboardProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Select dashboard view with Id equal to SchoolId from event</span> <span class="token function">Project</span><span class="token punctuation">(</span><span class="token keyword">event</span> <span class="token operator">=></span> <span class="token keyword">event</span><span class="token punctuation">.</span>SchoolId<span class="token punctuation">,</span> Handle<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>StudentAddedToSchool <span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">SchoolDashboard</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> view<span class="token punctuation">.</span>Students<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">event</span><span class="token punctuation">.</span>Student<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// query by name</span> <span class="token class-name"><span class="token keyword">var</span></span> name <span class="token operator">=</span> <span class="token string">"John Smith"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> students <span class="token operator">=</span> database<span class="token punctuation">.</span>Students<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>s <span class="token operator">=></span> s<span class="token punctuation">.</span>Name <span class="token operator">==</span> name<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> </li> <li><strong>Publish the event with only identifiers of dependent objects.</strong> E.g. <em>StudentAddedDoSchool</em> with from <em>StudentId</em>, <em>SchoolId</em>, but also the student’s data with the parents’ identifiers. In this case, if we want to have a denormalised reading model, we can load dependent data (e.g. parents’ names) at the moment of applying the projection. <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">StudentWithParentIds</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> MotherId<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> FatherId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">StudentAddedToSchool</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> SchoolId<span class="token punctuation">;</span> <span class="token class-name">StudentWithParents</span> Student<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">SchoolDashboardProjection</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Projection<span class="token punctuation">&lt;</span>SchoolDashboard<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token class-name">Database</span> database<span class="token punctuation">;</span> <span class="token function">SchoolDashboardProjection</span><span class="token punctuation">(</span><span class="token class-name">Database</span> database<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>database <span class="token operator">=</span> database<span class="token punctuation">;</span> <span class="token comment">// Select dashboard view with Id equal to SchoolId from event</span> <span class="token function">Project</span><span class="token punctuation">(</span><span class="token keyword">event</span> <span class="token operator">=></span> <span class="token keyword">event</span><span class="token punctuation">.</span>SchoolId<span class="token punctuation">,</span> Handle<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>StudentAddedToSchool <span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">SchoolDashboard</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> mother <span class="token operator">=</span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Load</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Parent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">event</span><span class="token punctuation">.</span>Student<span class="token punctuation">.</span>MotherId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> father <span class="token operator">=</span> database<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Load</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>Parent<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">event</span><span class="token punctuation">.</span>Student<span class="token punctuation">.</span>FatherId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> student <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">StudentWithParents</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> <span class="token keyword">event</span><span class="token punctuation">.</span>Student<span class="token punctuation">.</span>Id<span class="token punctuation">;</span> Name <span class="token operator">=</span> <span class="token keyword">event</span><span class="token punctuation">.</span>Student<span class="token punctuation">.</span>Name<span class="token punctuation">;</span> Mother <span class="token operator">=</span> mother<span class="token punctuation">;</span> Father <span class="token operator">=</span> father<span class="token punctuation">;</span> <span class="token punctuation">}</span> view<span class="token punctuation">.</span>Students<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span>student<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// query by name</span> <span class="token class-name"><span class="token keyword">var</span></span> name <span class="token operator">=</span> <span class="token string">"John Smith"</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> students <span class="token operator">=</span> database<span class="token punctuation">.</span>Students<span class="token punctuation">.</span><span class="token function">Where</span><span class="token punctuation">(</span>s <span class="token operator">=></span> s<span class="token punctuation">.</span>Name <span class="token operator">==</span> name<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> </li> <li><strong>Publish the event as in point 2, but do not read the dependent data.</strong> Then we keep the normalised data. So classically like in relational databases. Thus we’ll join data (or “do lookup” if we are using the document database) while reading. <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">StudentWithParentIds</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> Id<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> MotherId<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> FatherId<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">StudentAddedToSchool</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> SchoolId<span class="token punctuation">;</span> <span class="token class-name">StudentWithParents</span> Student<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">SchoolDashboard</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">string</span></span> SchoolId<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">string</span></span> Name<span class="token punctuation">;</span> <span class="token class-name">StudentWithParentIds<span class="token punctuation">[</span><span class="token punctuation">]</span></span> Students<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">class</span> <span class="token class-name">SchoolDashboardProjection</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">Projection<span class="token punctuation">&lt;</span>SchoolDashboard<span class="token punctuation">></span></span></span> <span class="token punctuation">{</span> <span class="token function">SchoolDashboardProjection</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Select dashboard view with Id equal to SchoolId from event</span> <span class="token function">Project</span><span class="token punctuation">(</span><span class="token keyword">event</span> <span class="token operator">=></span> <span class="token keyword">event</span><span class="token punctuation">.</span>SchoolId<span class="token punctuation">,</span> Handle<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>StudentAddedToSchool <span class="token keyword">event</span><span class="token punctuation">,</span> <span class="token class-name">SchoolDashboard</span> view<span class="token punctuation">)</span> <span class="token punctuation">{</span> view<span class="token punctuation">.</span>Students<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">event</span><span class="token punctuation">.</span>Student<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// query by name</span> <span class="token class-name"><span class="token keyword">var</span></span> name <span class="token operator">=</span> <span class="token string">"John Smith"</span><span class="token punctuation">;</span> <span class="token comment">// two joins needed</span> <span class="token class-name"><span class="token keyword">var</span></span> students <span class="token operator">=</span> database <span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span>database<span class="token punctuation">.</span>Parents<span class="token punctuation">,</span> student <span class="token operator">=></span> student<span class="token punctuation">.</span>Mother<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> mother <span class="token operator">=></span> mother<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> <span class="token punctuation">(</span>student<span class="token punctuation">,</span> mother<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> student<span class="token punctuation">.</span>Mother <span class="token operator">=</span> mother<span class="token punctuation">;</span> <span class="token keyword">return</span> student<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">Join</span><span class="token punctuation">(</span>database<span class="token punctuation">.</span>Parents<span class="token punctuation">,</span> student <span class="token operator">=></span> student<span class="token punctuation">.</span>Father<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> father <span class="token operator">=></span> father<span class="token punctuation">.</span>Id<span class="token punctuation">,</span> <span class="token punctuation">(</span>student<span class="token punctuation">,</span> father<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> student<span class="token punctuation">.</span>Father <span class="token operator">=</span> father<span class="token punctuation">;</span> <span class="token keyword">return</span> student<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">.</span><span class="token function">ToList</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> </li> </ol> <p>Each of these options has pros and cons:</p> <ol> <li>The first option is the fastest in processing because all data is already available in events. However, the redundancy of data in events makes them <em>“heavier”</em> to store and transport. Additionally, such events are more challenging to maintain since their schema is more likely to change. The more information they have, the greater the chance that their structure will change.</li> <li>The second scenario makes the events smaller, but the projection itself will take longer due to the need to load additional data. Asynchronous processing (and thus eventual consistency) should be considered here. Like the first solution, this solution’s advantage is that the end-user read will be fast because the data is denormalised. However, there is also a greater chance of hitting situations when, for example, the parent’s surname changes, and we will have to update multiple records in the <em>ParentNameChanged</em> event. We always have to analyse the structure of our data. It will not be costly for school and parents because one parent rarely has more than 1-3 children in one school. However, in a scenario where, e.g. tax changes and you need to recalculate a lot of transactions, this may be meaningful.</li> <li>The third solution is the fastest in terms of processing and data transfer but the slowest in reading. Both the events and the projections themselves won’t have redundant data. For this reason, we will have to correlate data when reading. It is not always a big problem, but database joins always have a negative performance impact. However, if we have good data distribution and index it well, this should also be a good enough solution for many scenarios.</li> </ol> <p>Between these cases, there is also grey area. Unfortunately, we won’t escape the classic <em>“it depends”</em> answer. Each case is different, and if we want a good, efficient yet sustainable solution, we need to consider the final usage scenarios. The above points are heuristics and advice that I hope can help you to design your solution.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. I encourage you to read my blog post <a href="/en/how_to_do_event_versioning/">“How to (not) do event versioning”</a>, which I think is an excellent complement to this one you just read.</p><![CDATA[How to get started with Open Source?]]>https://event-driven.io/en/how_to_start_with_open_source/https://event-driven.io/en/how_to_start_with_open_source/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/4434cc66d88ec4254e08391e3e5b015f/d2429/2020-03-31-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAAB30lEQVQoz3VS70tTYRjdnxb0QSj6kKREGdKHoiJxgsMLpY3raLSJ88emYLWs6NaGc5bb7p01F42UNgeVis796L7ve5/7vvcHTupDUAQxr93ujM6n8+E5nPM853ERQvAhDgkyDD0riV7e16hX45mU2z+deLmsNarmwVeFKNaYJSGEuIgDsiyb5n4yle45fVKYf3B7ItLnuX7qTHfgam/kLler7gEAObL5R6wAANqtYDLsD7xbf3+e4z1D/U8GBwN9vVzXiY/lImWabf5XjJEMqvGpGFsVOpKxUDieeZZIeCcf9QejHB+cC08+ng2DojiELfMjMUJIN/azr6LZ0KUXs7zb93BpofhrN3pQWxkfD/V0dYqiZJomQsiZ1GUfDGMMQCVRjAnz3TcnpiOJn2Uv7ORHh0duXDy3sVGilNpia/5PbIyAaVuF/MzYPffAwNP7U6P8HSm39u37j6VUWsq/Npq6M3P7zhirjG5+KM75bnmuXU4uxEMjQ59X018YlNZy6edCJlMCYIS0V2Vfr9VTs/m2UAgEx2qVnXJuudKQtxEN+7krF84mF9dVVcMYYQfaqsIIqQCUMVCUuqq92a6D0VzJxgRhirH/xLY/zOIIIYxbhKmgEExVpmu6FfjYh/0GXFUJ8AoJ2K4AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/4434cc66d88ec4254e08391e3e5b015f/a331c/2020-03-31-cover.png" srcset="/static/4434cc66d88ec4254e08391e3e5b015f/36ca5/2020-03-31-cover.png 200w, /static/4434cc66d88ec4254e08391e3e5b015f/a3397/2020-03-31-cover.png 400w, /static/4434cc66d88ec4254e08391e3e5b015f/a331c/2020-03-31-cover.png 800w, /static/4434cc66d88ec4254e08391e3e5b015f/d2429/2020-03-31-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>As you may know, I’m an active Open Source contributor. Since December 2020, I’m working full-time on the Open Source project <a href="https://eventstore.com">EventStoreDB</a>. I have been the co-maintainer of the quite mature and relatively popular .NET library - <a href="https://martendb.io/">Marten</a> for the last two years.</p> <p>How did it start? I could say it was in the normal way. I went from a user to a contributor to a maintainer. How exactly? You can read the details in my blog post <a href="https://event-driven.io/en/revolution_now/">“Revolution Now!’</a>. TLDR - I started as a user, then I found some missing pieces. I told myself: “why not try fixing that?“. Then I became an active community member, contributed more, and here I am. It was a step by step process.</p> <p><strong>What advice do I have on starting your journey with Open Source?</strong></p> <ol> <li><strong>Start with something small.</strong> It’s not the best idea to start with massive PR without discussing it with the maintainers. I began like <a href="https://github.com/JasperFx/marten/pull/841">this</a>. It was not the most prudent move. It’s like kicking in the door of the western saloon. You’re risking a defensive attitude from the maintainers or simply not meeting the repository convention. It is best to report an issue or an idea and ask for tips. You can add that you will be happy to do PR. Start with a simple Pull Request, describe it well, don’t just push the code. Maintainers love <a href="https://github.com/JasperFx/marten/pull/1344">small changes</a>, especially to documentation. By starting this way, you can even <a href="https://github.com/dotnet/corefx/pull/37611">become a .NET core contributor</a>. The time will come for bigger things. Don’t know what to add? Search the “TODO” in the code: these are good places to start. The maintainers often put the label “Up for grabs” on issues, indicating something that can be a good start.</li> <li><strong>Join the community.</strong> Every popular library has its communication channel, be it Slack, Gitter, Discourse or a mailing list. Join it, check how people communicate with each other and how they help on issues. Verify if this is the place you want to be. From such a channel, you can also assess whether the community is alive. If there are active discussions, there is a greater chance that the library is maintained.</li> <li><strong>Help others.</strong> Open Source, as the name suggests, is about being OPEN. Ask, rejoice, but also help. Even if you do not consider yourself an expert, your advice may be valuable to someone. Don’t be afraid that someone will tell you that you’re wrong. Even if your suggestion is not optimal and someone criticizes it, you will at least learn something new. You will confront your thinking.</li> <li><strong>Don’t be demanding.</strong> You should remember that there is a human on the other side who usually does it out of his passion and at the expense of other stuff. Can you see that something is missing? Contribute.</li> </ol> <p><strong>Do you want to create your Open Source library?</strong></p> <ol> <li> <p><strong>Do something that has value for you.</strong> Don’t create the Wunderwaffe. Think about your problem and solve it.</p> </li> <li> <p><strong>Deliver in small pieces.</strong> Do not bury your work on the long-living branch. There is a high risk that you will never finish that. Break it up into smaller pieces and deliver on each piece, one by one. There is nothing more motivating than to finish something</p> </li> <li> <p><strong>Take care of the documentation.</strong> I can assure you that without it, no one will use your library. It’s not that hard: for example, Github gives you the option of automatic documentation generation from MD files with Jekyll. At the very least, make a decent README. Also, check out my other article <a href="/en/how_to_successfully_do_documentation_without_maintenance_burden/">“How to successfully do documentation without a maintenance burden?”</a>.</p> </li> <li> <p><strong>Create a CI / CD flow.</strong> Many tools allow you to configure a free CI process for Open Source projects expressly: Azure DevOps, AppVeyor, Github Actions, Travis, etc. It can be a basic one. It would be good if it’d check at least if code is building and tests are passing. This is crucial for potential contributors. Nothing is more discouraging than a failing project or needing to make a Build Dance to get started on the code-base. If you’re searching for inspiration, check my article <a href="/en/how_to_setup_a_test_matrix_in_xunit/">“How to set up a test matrix in XUnit”</a>.</p> </li> <li> <p><strong>Take care of backward compatibility</strong> This is one of the essential elements of creating libraries. The stability and predictability of the API is crucial. If you keep posting Breaking Change now and then, users will either not move to newer versions or quit. Follow the <a href="https://semver.org/">Semantic Versioning</a> rules. Unfortunately, this requires one thing: thinking before coding to not release anything that you will regret later. Read also more in my article <a href="https://event-driven.io/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">“Let’s take care of ourselves! Thoughts on compatibility”</a>.</p> </li> <li> <p><strong>Less is more.</strong> Focus on the little things, deliver fewer features, but better quality. It also applies to technical details: only expose what you want users to use. If you inadvertently make a class public, then if you’re going to throw it out, you may be surprised by how many users are using it and how weirdly and wonderfully they are using it.</p> </li> <li> <p><strong>Don’t expect to get money out of it.</strong> The harsh reality shows that working on OSS is philanthropy. I’m lucky to be working right now on EventStoreDB, which has managed to find a way of maintaining full-time employees (watch our CEO Dave Remy explaining that: <a href="https://www.youtube.com/watch?v=HpiPqWmilN4">https://www.youtube.com/watch?v=HpiPqWmilN4</a>). However, it’s a rare case. From my work on Marten, I managed to get €0. Don’t expect to be paid. The subject of “Open Source Sustainability” is a broad topic, even for a separate email or blog post (if you want me to write about it, let me know!). For now, take a look at the <a href="https://www.wired.com/story/when-open-source-software-comes-with-catches">controversy with the Redis license</a>, a <a href="https://github.com/zloirock/core-js/issues/548">discussion about how the creator of one of the main npm packages started asking for support</a>. There are initiatives such as <a href="https://opencollective.com/">Open Collective</a>, <a href="https://github.com/sponsors">Github Sponsors</a>.</p> <p>I’ve set-up also sponsorship accounts there:</p> <ul> <li>Github sponsors - <a href="https://github.com/sponsors/oskardudycz">https://github.com/sponsors/oskardudycz</a></li> <li>Open Collective - <a href="https://opencollective.com/eventsourcingnetcore">https://opencollective.com/eventsourcingnetcore</a></li> </ul> <p>There is already a group of people who appreciated my work enough to become financial supporters. Still, it’s very far from being a good source of funding. So far, it is mainly a form of thanks for my work, and it makes me very happy!</p> <p>Therefore, before you start working with Open Source make sure that it suits you. I encourage you to read the excellent post about the fact that Github Stars won’t pay rent: <a href="https://medium.com/@kitze/github-stars-wont-pay-your-rent-8b348e12baed">link</a>.</p> </li> <li> <p><strong>Promote your work.</strong> Writing blog posts (like this one you’re reading) helps readers learn from your experience, but it also helps increase awareness of your work. Create your <a href="https://github.com/oskardudycz/">GitHub page</a>, <a href="https://twitter.com/oskar_at_net">write on Twitter</a>, etc. I assume that you created something because you believe that’s useful. Explain the people and help them solve their problems. Be active in the community. Marketing takes time, it’s far from something that we developers like to do, but it’s unavoidable if you want to spread your works’ usage.</p> </li> <li> <p><strong>Be prepared for non-pleasant situations.</strong> You can find hate in the OSS world, unhealthy and drama situations like the React community event: <a href="https://dev.to/aryanjnyc/ken-wheeler-and-dan-abramov-deactivate-their-twitter-accounts-302">take a look here</a>. I was lucky enough to be removed from such events. But it can happen to you. If you expose your work publicly, then you should expect criticism. Still, cheer up! There are cases like this: <a href="https://github.com/JasperFx/marten/issues/1347">link</a> that can put you on cloud 9.</p> </li> </ol> <p><strong>If you want to get started with OSS, you can check one of my repositories, e.g.:</strong></p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore">https://github.com/oskardudycz/EventSourcing.NetCore</a> - if you want to learn or share through examples of Event Sourcing, DDD and similar things,</li> <li><a href="https://github.com/oskardudycz/GoldenEye">https://github.com/oskardudycz/GoldenEye</a> - if you want to work on a .NET framework that makes it easier to work with WebAPI, DDD, etc.,</li> <li><a href="https://github.com/oskardudycz/WebApiWith.NETCore">https://github.com/oskardudycz/WebApiWith.NETCore</a> - if you want to share your knowledge or learn something about doing a well-shaped WebApi,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.NodeJS">https://github.com/oskardudycz/EventSourcing.NodeJS</a> - if you want to learn how to create WebAPI in NodeJS together with Event Sourcing.</li> </ul> <p>How can you help?</p> <ul> <li>add an issue with what you do not understand,</li> <li>addition or corrections to documentation,</li> <li>new samples,</li> <li>new features,</li> <li>bugfix.</li> </ul> <p>Of course, the best thing to do before a significant change is to contact me. If you have no idea but want to do something, also speak up.</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. If you’re also considering blogging, have a look at my <a href="/en/thoughts_on_tenth_blogging_anniversary">“10 notes on the 10th blogging anniversary”</a>. I’d like to know them before I start!</p><![CDATA[How to successfully do documentation without a maintenance burden?]]>https://event-driven.io/en/how_to_successfully_do_documentation_without_maintenance_burden/https://event-driven.io/en/how_to_successfully_do_documentation_without_maintenance_burden/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/f006e91394254e8f05497ab66646921b/d2429/2021-03-24-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AO/28sfWxbfDrZqnh7K+n+bl4NXSxrazoYuIdYF8auTTyvLs4ujo2MPEq7q5orOukb28osvIr8XCrpeQegB9gnBmZk95dVmCfmRVWzpXWjphXElZVkBoYUhAOyVhT0eHg3h2e2x5fXVWVktSNhpONyJPOSNSPipDLBsAW0U1mY2BmY+CnZeOZVpTTD0qWko2XUk2e2RUQCskbnFtaGllbWxpgIB8VktDSCoTZE87VUEwUj4tU0AxAKOai5qOfZ2Kd4lyXkEuH2BNQHRaSYJiTnBSPjcZFYpVUZFmYo9gWp1mYn9RSWNVPmNZRYl/cKijmJOOggCWh3WcjHiMfGx1XkozIhUpJRo+MyNIOSk1LyA8IxiVcF6gh3affGuggnhzY1iEgXJ1cmKBfW+ioJRoW0wAVVZGXllLZFxMUko6MikfS0ItYlxKbWdVWldFUEY5dmpfgnhrgnhteHNpODYwPzkxYlI/S0IvWVRITkU3AFlUQV9YTGJbTVZQRD45KmtmU3x6aXVuYGZSRU1HPGtkXXl4cXh3cWpkXTQsJTgzJmljSmpjTFlTP1NKMwCIj3Z3fWiBhXBiaFUoHxxZNTFhNytkPzJcPTNKNy5/dWePeW+Ne2qdkHtwYVFCPTJoZFNhW0xZU0VANScAqbOeoq+bo7CcZmhXNR8dZkVJeVFRcVBRVzg5U0A1jo52iYVrl5N5urqqWVpOY2FUhoN0hn1vgHlrXVVKAHNfUHVnWWtcT1ZBNCEUCzw7LUhDMz48LisqH1hXSqysmqemk52djKywpWdmWnZwWpmXf5GNdnl4YXFxWgCBXEaLa1iaemJ/XD84JRuWdGCpjXuLemx6dG1yX1OCc1d7cltwYk16ZlJiUUZyXkqNeV2EdF2Ce2VuaVdEIwwPIOkGXwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/f006e91394254e8f05497ab66646921b/a331c/2021-03-24-cover.png" srcset="/static/f006e91394254e8f05497ab66646921b/36ca5/2021-03-24-cover.png 200w, /static/f006e91394254e8f05497ab66646921b/a3397/2021-03-24-cover.png 400w, /static/f006e91394254e8f05497ab66646921b/a331c/2021-03-24-cover.png 800w, /static/f006e91394254e8f05497ab66646921b/d2429/2021-03-24-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Developers like to complain about the lack of documentation. They complain even more when they have to write it. No programmer wants to do this - neither do I! Rarely, projects manage to keep the documentation up to date. It’s rarely a high priority comparing to other tasks. Quite often, it’s left for “later on” that never comes. Even though it might be tiring, I love the documentation. There is nothing better than well-kept documentation when you join a project. Moreover, there is nothing better than descriptive documentation when you return to an old topic you didn’t touch for months.</p> <p>I’ll share some ideas that can help in keeping your documentation up to date:</p> <ol> <li> <p><strong>Keep your documentation in the repository:</strong> - no Confluence, no Sharepoint, no network share! What’s more, keep things simple, so no Word documents etc. The most straightforward approach is to user Markdown (<em>.md</em>) files. It allows text formatting in a simplified way that should be good enough for docs. Tools like GitHub and GitLab can show you a formatted view. They also allow editing and co-authoring it through the user interface. Additionally, contrary to Word (and other binary formats), it can be easily versioned and diffed. Many free tools allow generating static HTML sites that can be hosted even on the simplest web server. If you use Github, you also get free hosting for free with GitHub pages. You can also use <a href="https://www.netlify.com/">Netlify</a>, an excellent free static HTML hosting service. We’re using it in EventStoreDB and Marten. I’m also using it for my personal blog. In EventStoreDB, we’re using <a href="https://vuepress.vuejs.org/">VuePress</a>, in Marten, <a href="https://storyteller.github.io/documentation/docs/">Storyteller</a>. Both tools allow allows us to dynamically attach code snippets directly from source code. For the blog you’re reading, I’m using <a href="https://www.gatsbyjs.com/">GatsbyJS</a>. You can also use, e.g. <a href="https://jekyllrb.com/">Jekyll</a>, <a href="https://gohugo.io/">Hugo</a>.</p> </li> <li> <p><strong>Include documentation in the Code Review process:</strong> Just putting the documentation together with the source code will not be enough. What is the actual advantage of this over, e.g. Confluence? If it’s in the repository, then it allows you to verify whether the changed logic is updated in the documentation. You can view the history of changes and check what code change triggered the update or how documentation evolved. It’s much easier to maintain documentation regularly than from time to time with batches. Especially retrofitting is a nightmare process. Using the code review tools gives us the option to have efficient async discussions. I don’t know if it’s just me, but for me, commenting (having a thread conversation) on Confluence is a tragedy. It is much easier to refer to a specific line of code in Github or Gitlab. Keeping all discussion in the same place improves discoverability by having a single source of truth about both technical and business decisions.</p> </li> <li> <p><strong>Create immutable documentation:</strong> A few years ago, Michael Nygard wrote an article <a href="https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions">“Documenting architectural decisions”</a>. He presented the Architecture Decision Record idea. It’s a concise record of our architectural decisions. It should include:</p> <ul> <li><strong>Title:</strong> what is the change about.</li> <li><strong>Context:</strong> where the need came from. We describe here the background for the decision and facts about the current state. It can describe both your business case and technical one.</li> <li><strong>Decision:</strong> proposed solution for our issue.</li> <li><strong>Status:</strong> e.g. proposed, accepted, rejected - yes, we also catalogue rejected ideas. They carry a substantive value about our thought process. We can also get back to rejected ideas when the context changes, and we can analyze them again.</li> <li><strong>Consequences</strong> - positive and negative effects of the decision. It is essential not to be putting lipstick on a pig. We should show the proposed solution’s opposing sides and risks.</li> </ul> <p>It’s also important to do it transparently and invite all the stakeholders to review it. In my previous project, we were sending such items to the general forum (slack channel). Everyone was informed and invited to add their two cents in the review. It is advisable that people from other teams also speak. This change may often concern them directly (e.g. changes in shared contracts) or indirectly (a global architectural change, e.g. authorization). An additional aspect is that we may fall into tunnel thinking in a team. A fresh look can spot mistakes or missing pieces. It’s not about patting your back when making decisions but about making the best decision we can. Now the final, crucial part. The approved document cannot be changed. If we found out that we need to update the decision, we should send a new ADR. Thanks to that, once added, the documentation does not have to be updated and maintained. We’re just recording the next set of decisions.</p> </li> <li> <p><strong>Automate the process</strong> - As I mentioned already, there are so many cool and simple tools that can make the process painless. There is no need to write some custom scripts or do it all on your own.</p> </li> </ol> <p>Having good documentation is also crucial if you’re building the tool that you’d like to be used by others (e.g. library or framework). I can guarantee you that the chance that it’ll be used is close to zero without good docs.</p> <p>I also encourage checking my <a href="/en/architect_manifesto/">Architect Manifesto</a> in which I describe how to put it into the overall architecture process.</p> <p>I hope that helped. What’s your approach?</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[How money in Cloud impacts Architectural decisions?]]>https://event-driven.io/en/how_money_in_cloud_impacts_architectural_decisions/https://event-driven.io/en/how_money_in_cloud_impacts_architectural_decisions/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c38c460a45602bcf9ccb70e7c4829cd3/d2429/2021-03-17-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABOklEQVQoz42S30vCUBSA/Sd79iXIiB56qifJTPKhl6JeejAwlBG5XgKlFmXmj9wSJLemTpFhw7mQGeHV7d57IgeLcLQO9+nc853vcO4NwEJQSgFgOBi+NmSxLvWUnjWzwCsCntm21O40u2PzwxyZalct31csy/KBHacxMPiigCYIoamTlxuyrulugTdMCAGA53INIcQ/COHl7Rybsy3bHJlCSXAL/jIrsqJrOsX0IHK4trS+E4oKJaFa5H3MML9CE5S/zvfVPraxoRvZiyx3xbXElo/Z7T1+N3fje0+FaiaZ4W5uZan5r21jggGAubvcOo6kE+ng5soJczr7nC6M7AU7gz3WK6H4RjC2mmLPo8n9BJea9yX+7+zwBb54xjLhoxj/UhM7MnipvT8JoYR+H/qmaTbGP8v8DX8BFC9l9fXwDb0AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/c38c460a45602bcf9ccb70e7c4829cd3/a331c/2021-03-17-cover.png" srcset="/static/c38c460a45602bcf9ccb70e7c4829cd3/36ca5/2021-03-17-cover.png 200w, /static/c38c460a45602bcf9ccb70e7c4829cd3/a3397/2021-03-17-cover.png 400w, /static/c38c460a45602bcf9ccb70e7c4829cd3/a331c/2021-03-17-cover.png 800w, /static/c38c460a45602bcf9ccb70e7c4829cd3/d2429/2021-03-17-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>It’s intriguing how our perspective on software development changed in the last few years. We transformed from the on-premise age to the cloud era. Cloud is no longer a “buzz word”. When someone says “I use Azure”, “I use AWS”, we acknowledge it with a shrug. When the solution matures, we stop seeing it in black and white. We notice different shades of grey and begin to understand better features, characteristics. In “A Wizard of Earthsea”, Ursula Le Guin wrote that we gain control over someone when we find out their real name. Did that happen already with Cloud?</p> <p><strong>The cloud has several names, one of which is money.</strong> We pay for every second of computing and data transfer. Nothing comes for free. We know perfectly well that the more we use Cloud, the more we’ll pay. Apart from this obvious fact, others are not visible at the beginning. We may miss the impact of those continuous costs on our architecture. Some time ago, life was simpler. We didn’t have too much choice. We were usually selecting out of a few databases. Most of them were relational. If we were a .NET shop, we were using MSSQL. If we had a lot of money, then we could use Oracle. If we wanted something for free, we used MySQL or Postgres. If we were brave enough and progressive to use a document database, we used Mongo or RavenDB.</p> <p>The main cost was made when the project was kicked off. We were buying the licence, server machine and then we forgot about the cost. If the database was Open Source, the programmer could be a decision-maker. Now, in the cloud, even for Open Source technologies, we have to pay. We have more options, but that makes a choice more difficult.</p> <p>An example from my previous project. We used AWS. We’d like to use DynamoDB extensively for everything in the write model. However, we weren’t using it everywhere. We used it mainly for configuration data and operations that had to be fast. We used AuroraDB for business data - a distributed relational database. <strong>Why? Because it is cheaper and it is good enough for our problem.</strong> Therefore, architectural decisions can no longer be made at the convenience of developers. They also have to be made based on Excel calculations.</p> <p><strong>Another aspect is performance.</strong> We quite often relegate considerations about that at the very end of the project. Let’s say that 1 second for a reply from our endpoint is “good enough”. Assume that we didn’t focus on that, and our request takes 10 seconds. We may not be paying attention, but we may realise that we are paying 10 times more for our services than we should. For a SASS startup, this can often turn out to be an important issue - to be or not to be. Those considerations should impact our strategy of dealing with performance.</p> <p>Yet another curiosity. It is worth being good at math. In serverless, we can select the size of resources for lambda/function. The more resources we allocate, the more we will pay for the processing time. But! It turns out that it might be more profitable to allocate more resources because they will process the request faster and ultimately pay less.</p> <p><strong>I bet that soon a consultant with an abacus will be a well-paid profession.</strong> This person will come with the Cloud catalogue. We will tell what we need in our system, and the consultant will spit out a cost-optimal set of cloud services.</p> <p>What are your experiences in this area?</p> <p>Cheers!</p> <p>Oskar</p> <p>p.s. I also wrote my thoughts on the Cloud security in article <a href="/en/form_a_wall/">“Form a wall! And other concerns about security”</a>. Have a look!</p><![CDATA[Can command return a value?]]>https://event-driven.io/en/can_command_return_a_value/https://event-driven.io/en/can_command_return_a_value/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/8110f47ddc60012d9b9f7186089a8b9e/d2429/2021-03-10-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACVUlEQVQoz2NgZGRkQAaMIMAARoyMjExMTAwMDPIyUvo62qwsLAyMjKwc7AwszPpuLpLqagxMzExcIsLs/HwcgoJMLCxIJoAMhWiOCQ+9fOKIrbU1AwODjqODjp1tem9XQnMDA4+wsLiGuoCMtJiqMis7OwsbKxsXFysHOxMzEyMTIwvYOGsjg91zJ9rqazIwMrlEh7unp2x88WTGyWMM6jaW+t4eZkH+rqnJOg52VoH+2rbWThFhlr7ePIKCDAwMXELC1uEJnv4hWQ0trknpxalx4Xk5jevXdm/awCBnbCiioqSkp6tjbSmjp6Pu5MArIa5ha6Pt7MTGzs7AxhHaNLlo68mZVx7OO3t95rFjUXnphjER3onR/BLiDCJqKmqGBsZOjtbuLs6O1jrG+uIS4no6Ou7urqJqKhnFxROOn7SryujZumD+zuWz1s3Qcddz9NBoyYmS11BjYGJgUNfQMrY1yyz2dfX0NFNXMTUyjAoLyUiNj2hr3TZ/ckxKdNuctn2H9zx79uTevYcxCZlsrGx8rMzMkEhRVVfXNzfX0NNW1dBkgQU4AwMDKzMzCyODkaH5wYMnv3z78f///2MnLzvaOAuLiUNihIGRCRQlvLx83Nw8ECEmZmZmFhYWVlZmsEEuhupXLlw4c+rM1lXL163ZmB3gIMzNBtOMlkiQADMzMwMDQ4iX649v365cvX7hwuVr128GO1kxgrWCVLBxc7JysLPzcLHz8bBwcoAQNycrDycrNwc7LzcDA0NlTe3//////Pnz+9//379/J0eGQnSxcLABACS0mEAD6JyxAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/8110f47ddc60012d9b9f7186089a8b9e/a331c/2021-03-10-cover.png" srcset="/static/8110f47ddc60012d9b9f7186089a8b9e/36ca5/2021-03-10-cover.png 200w, /static/8110f47ddc60012d9b9f7186089a8b9e/a3397/2021-03-10-cover.png 400w, /static/8110f47ddc60012d9b9f7186089a8b9e/a331c/2021-03-10-cover.png 800w, /static/8110f47ddc60012d9b9f7186089a8b9e/d2429/2021-03-10-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Last week I <a href="/en/cqrs_facts_and_myths_explained">busted common myths and explained facts about CQRS</a>. Today I’ll continue my effort. I tackle one of the most common questions about CQRS: <em>“Can command return a value?”</em>.</p> <p>In programming, using phrases like <em>“must not”</em>, <em>“should not”</em>, <em>“never”</em>, <em>“it cannot be done”</em> is very risky. What we said yesterday may hit us tomorrow. I experienced that more than once. For CQRS, I used a mental shortcut several times, saying <em>“the command cannot return a value”</em>. Never ever? It depends.</p> <p><strong>What does <em>“returning a value/result/data”</em> mean?</strong> One of the basic concepts of programming is a function. The function takes the input data, performs the logic and returns the result. Simple as that? Not always.</p> <p>Things can get more complicated. Some programming languages allow defining the function result as <em>void</em> or <em>undefined</em>. In theory, it means that there is no result. In practice, it means that the operation has been successful. This is, in fact, an equivalent of returning boolean <em>true</em> value.</p> <p>What happens when an operation was unsuccessful? In many languages: we throw an exception. In functional programming or languages like Go, Rust, it’s mandatory to always return the result even in case of failure. Why? Returning the result is explicit. You know what you expect. Using <em>void</em> or throwing an exception is not clear enough. Java forces you to handle all exceptions and declare them on the method’s signature. In C# or JavaScript, you have to guess what set of exceptions may be thrown. In fact, throwing an exception is also a type of result, just implicit (as we’re expected to handle that somewhere).</p> <p><strong>Now we already know that the function from <em>void</em> does not mean that it returns nothing. It returns an implicit result.</strong></p> <p>Useful APIs require expressiveness. HTTP-based API has a whole set of return statuses - both successful operations and errors.</p> <p>So can command return a value ​​or not?</p> <p>In CQRS, commands and queries segregation is based on the operation behaviour. Query returns data and does not change the application’s state. Command modifies the state. Such segregation helps create loosely coupled components, evolving solutions, reducing the programmer’s cognitive load by focusing on a specific task, scalability, etc.</p> <p><strong>Is there a use case that would justify returning a value from command handling?</strong> For sure, we should not return the information that was sent from the client. The client already knows it. What the client does not know is the data generated/calculated on the server-side. Usually, it is metadata such as modification date, version, sometimes also business data (e.g. new record status).</p> <p>We know the object identifier during the update. When it’s finished, we can query the appropriate read model after executing the command to get new data. New object creation is more complicated. Unless we generate an identifier on the client-side, we won’t know which record was created without returning the new record’s id.</p> <p><strong>We have to remember that we can look at our application from two perspectives:</strong></p> <ul> <li><strong>Logical</strong> - shows how we divide and structure a business code.</li> <li><strong>Technical</strong> - describes how a specific implementation is organised, e.g. the storage structure, deployment etc.</li> </ul> <p>CQRS represents a logical view, API a technical one. They don’t have to be the same, and we might need to map one concept to another. If we are doing a REST API, then after handling a command that creates a new record, we would like to return <em>201</em> status with an identifier. If the error was thrown/returned by the business logic, we would like to get a specific HTTP error type.</p> <p><strong>We have to make pragmatic decisions. In my opinion, it’s okay to return id or status or any other needed metadata related to the behaviour we triggered.</strong> Of course, we should be careful and always make sure that such compromise is required. If we abuse that, then the critical element of CQRS - Segregation will be contractual at best. This can impact not only the purity and coupling of our code but also performance and scalability. Read models may be handled by different nodes, storage engines, load-balanced etc.</p> <p>We must remember that the command itself does not tell us what specific change it makes. It does not define whether the record will be added, updated or removed. What’s more, it does not determine whether the change will affect one record or more. Even a single change may involve refreshing several read models (e.g. confirming the order updates the user’s orders list, administration panel for the seller, notification view, order history, etc.). All of this is defined by the business logic of handling the command, not the command itself. Command represents the intention, not the way it’s handled and what’s the impact it does.</p> <p>How am I handling CQRS with REST? Usually, I try to separate WebAPI contracts from the commands and queries definition. I’m treating WebAPI as an anti-corruption layer. In the controller method, I can generate an identifier and create the command based on that. The same identifier can then be returned to the Created status.</p> <p>See the sample for the creating the cinema seat reservation (sample is in C# but I hope that’s also straightforward for non-C# devs):</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">HttpPost</span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task<span class="token punctuation">&lt;</span>IActionResult<span class="token punctuation">></span></span> <span class="token function">CreateTentative</span><span class="token punctuation">(</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">FromBody</span></span><span class="token punctuation">]</span> <span class="token class-name">CreateTentativeReservationRequest</span> request<span class="token punctuation">)</span> <span class="token punctuation">{</span> Guard<span class="token punctuation">.</span>Against<span class="token punctuation">.</span><span class="token function">Null</span><span class="token punctuation">(</span>request<span class="token punctuation">,</span> <span class="token keyword">nameof</span><span class="token punctuation">(</span>request<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> reservationId <span class="token operator">=</span> idGenerator<span class="token punctuation">.</span><span class="token function">New</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> command <span class="token operator">=</span> CreateTentativeReservation<span class="token punctuation">.</span><span class="token function">Create</span><span class="token punctuation">(</span> reservationId<span class="token punctuation">,</span> request<span class="token punctuation">.</span>SeatId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> commandBus<span class="token punctuation">.</span><span class="token function">Send</span><span class="token punctuation">(</span>command<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token function">Created</span><span class="token punctuation">(</span><span class="token string">"api/Reservations"</span><span class="token punctuation">,</span> reservationId<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>I do not return any additional data in the case of an update/deletion. It is straightforward for the client to query the API again.</p> <p>For error handling, I usually make a processing error by throwing an appropriate exception and mapping it to HTTP status, e.g. via middleware:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">ExceptionHandlingMiddleware</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">RequestDelegate</span> next<span class="token punctuation">;</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">ILogger</span> logger<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">ExceptionHandlingMiddleware</span><span class="token punctuation">(</span><span class="token class-name">RequestDelegate</span> next<span class="token punctuation">,</span> <span class="token class-name">ILoggerFactory</span> loggerFactory<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>next <span class="token operator">=</span> next<span class="token punctuation">;</span> logger <span class="token operator">=</span> loggerFactory<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">CreateLogger</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>ExceptionHandlingMiddleware<span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token return-type class-name">Task</span> <span class="token function">Invoke</span><span class="token punctuation">(</span>HttpContext context <span class="token comment">/* other scoped dependencies */</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">try</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">next</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span> <span class="token punctuation">(</span><span class="token class-name">Exception</span> ex<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">await</span> <span class="token function">HandleExceptionAsync</span><span class="token punctuation">(</span>context<span class="token punctuation">,</span> ex<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token keyword">private</span> <span class="token return-type class-name">Task</span> <span class="token function">HandleExceptionAsync</span><span class="token punctuation">(</span><span class="token class-name">HttpContext</span> context<span class="token punctuation">,</span> <span class="token class-name">Exception</span> exception<span class="token punctuation">)</span> <span class="token punctuation">{</span> logger<span class="token punctuation">.</span><span class="token function">LogError</span><span class="token punctuation">(</span>exception<span class="token punctuation">,</span> exception<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> codeInfo <span class="token operator">=</span> ExceptionToHttpStatusMapper<span class="token punctuation">.</span><span class="token function">Map</span><span class="token punctuation">(</span>exception<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> result <span class="token operator">=</span> JsonConvert<span class="token punctuation">.</span><span class="token function">SerializeObject</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token constructor-invocation class-name">HttpExceptionWrapper</span><span class="token punctuation">(</span><span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>codeInfo<span class="token punctuation">.</span>Code<span class="token punctuation">,</span> codeInfo<span class="token punctuation">.</span>Message<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>ContentType <span class="token operator">=</span> <span class="token string">"application/json"</span><span class="token punctuation">;</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span>StatusCode <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">int</span><span class="token punctuation">)</span>codeInfo<span class="token punctuation">.</span>Code<span class="token punctuation">;</span> <span class="token keyword">return</span> context<span class="token punctuation">.</span>Response<span class="token punctuation">.</span><span class="token function">WriteAsync</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Check the full sample in my repo: <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/Tickets">https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/Tickets</a>.</p> <p>I generally suggest to keep our command handling <em>“clean”</em> and actually treat it as a <em>“void function”</em>. So the function that’s not returning the business result but can return operation status or needed metadata.</p> <p>Let me know about your approach. I’d love to find out what’s your perspective on that.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[CQRS facts and myths explained]]>https://event-driven.io/en/cqrs_facts_and_myths_explained/https://event-driven.io/en/cqrs_facts_and_myths_explained/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/a6cfed9676fe0329352695fe95800967/d2429/2021-03-03-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACl0lEQVQozxXES1MSAQAA4L32mOnQsdFDXTzkqRpLhkknNUsngzRhFDBRUZR4yWMfwi7CAgss++K5sLxBFEQEFcUsyxnTLv2kpu/wAXfu3ut90udDHAFwQaBRzKYqEfNl74Lqs/jF8/6XokG9XnNUykNmo0W/PiEdNZq1aSpIuj12kxG4/+Bh/7OX5Vjcb1dsWXVh51JAO5RDFzP6WWhCNDwyuLKqPC5kA6iVDsKgXQ3ZdCHEWmcDLGwBHj3uGx4bz0dT46PisRGRWS1xzL7iYQUqH7Z+GLCrpzin7rKcZ3GLC1rdsslJjwnSzFdJrBVCgZ6eXsn0VIVPL81MLk4OaufeKD4Nqd6LZwaebije7eOGqzh6EvOHNtWEZdkkFRFWrU41vY9bsrAOWFZKWB/Ek3h4c0lwbphlYsWcWPrxtVo+zrpWKz5jN+Zqxogmh18k8TKiOIoGcyFn0a1PgWuA5qvcYFGurUthgyzo0KyoxJhhmkKUStlbDjPVaazDE3XWvUu5GmEsa5Q0POv7rHePQkmbFkhHolEmhDnsuMOWj0eEBHdWq5f4tEKmqGWyzWLxslE/zCQPMomL3Uo7Tp3lYlWGaKdi3DYKXJ/c3J7fnDc63YPT6+6v392rs2aXDUd9Lk8hmTmpNRM0U+L5i71qKyd0dsrHeeHb//NVJgTUy62zwx9/fv7ttr+3q412tcazXJQk0zSdo6kCRweRTQa171KeCoXXhdRt5/i0UqrEYof5EpAimUIk0cwXCwxZ5KhEOIghMGgwEIgdh0HUajYtq3yQhfU4GAIncC8KgeV0eieTa+3UAAonaYLyuv242w/CGIhsI6gPwgjYFfCGogjq9Xj8qaRAUew2imk3jF8WNUwwnOEiNUH4BxG4Un6jYFO/AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/a6cfed9676fe0329352695fe95800967/a331c/2021-03-03-cover.png" srcset="/static/a6cfed9676fe0329352695fe95800967/36ca5/2021-03-03-cover.png 200w, /static/a6cfed9676fe0329352695fe95800967/a3397/2021-03-03-cover.png 400w, /static/a6cfed9676fe0329352695fe95800967/a331c/2021-03-03-cover.png 800w, /static/a6cfed9676fe0329352695fe95800967/d2429/2021-03-03-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>I’m sure that you know a lot of Urban Legends. They usually start with <em>“hey, my friend’s cousin saw that…”</em> or <em>“you know, I’ve read such stuff on the Internet…”</em>. Everyone has the right to have an opinion. Unfortunately, most people use it.</p> <p>Some time ago, my colleague from the US asked me, <em>“What kind of strange tree is it?”</em>. It appeared that he was pointing a finger at a tree without any leaves (it was autumn) but covered with mistletoe. It could quickly become a story about the weird nature in Poland. Such things can quickly go off the rails. We all know that a lie told once remains a lie, but a lie told a thousand times becomes the truth.</p> <p>Likewise, our technical patterns are filled with myths and mishaps. Quite often, this happens to CQS and CQRS.</p> <p>You can hear quite often that to use CQ(R)S:</p> <ul> <li>You need two databases.</li> <li>You have to use a messaging queue (e.g. RabbitMQ or Kafka).</li> <li>It’s hard to apply and complicates the architecture.</li> <li>You’ll be facing Eventual Consistency.</li> <li>You need to do Event Sourcing.</li> </ul> <p>I’ll tackle those myths today. But, First things, first! Let’s start with these enigmatic acronyms.</p> <p><strong>CQS</strong> stands for <strong>C</strong>ommand <strong>Q</strong>uery <strong>S</strong>eparation.</p> <p><strong>CQRS</strong>, <strong>C</strong>ommand <strong>Q</strong>uery <strong>R</strong>esponsibility <strong>S</strong>egregation.</p> <p>Do any of these names say something about two databases? Or two tables? Does it say anything about storage at all? Well, not really.</p> <p>Both CQS and CQRS have Command and Query in their name.</p> <p><strong>Command</strong> is a request (intention) to change.</p> <p><strong>Query</strong> is a request to return data.</p> <p>For example:</p> <p>The command to add a meeting could look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">CreateMeeting</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">string</span></span> Name <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name">DateTime</span> HappensAt <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> CreateMeeting <span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> name<span class="token punctuation">,</span> <span class="token class-name">DateTime</span> happensAt<span class="token punctuation">)</span> <span class="token punctuation">{</span> Name <span class="token operator">=</span> name<span class="token punctuation">;</span> HappensAt <span class="token operator">=</span> happensAt<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>A query to retrieve a specific event might look like this:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">GetMeeting</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">Guid</span> Id <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> GetMeeting <span class="token punctuation">(</span><span class="token class-name">Guid</span> id<span class="token punctuation">)</span> <span class="token punctuation">{</span> Id <span class="token operator">=</span> id<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>As you can see, these are ordinary DTO objects.</p> <p>CQS pattern was created by Bertrand Meyer during his work on the <a href="http://laser.inf.ethz.ch/2012/slides/Meyer/eiffel_laser_2012.pdf"> Eiffel language</a>. He stated that:</p> <p><strong><em>“Asking a question should not change the answer”</em></strong></p> <p>and defined:</p> <p><strong><em>“A command (procedure) does something but does not return a result.</em></strong></p> <p><strong><em>A query (function or attribute) returns a result but does not change the state.”</em></strong></p> <p>Simple as that. Still not talking about storage or a queue, right?</p> <p>With such a distinction, processing can be made more simple and predictable. A query won’t create any side-effects. A command won’t be used for requesting data.</p> <p>CQRS is an extension of CQS. Greg Young coined it as:</p> <p><strong><em>“Command and Query Responsibility 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.”</em></strong></p> <p>Still nothing about storage, right?</p> <p>CQRS can be interpreted at a higher level, more general than CQS; at an architectural level. CQS defines the general principle of behaviour. CQRS speaks more specifically about implementation.</p> <p><strong>Using CQRS, you should have a strict separation between the Write Model and the Read Model</strong>. Those two models should be processed by separate objects and not be conceptually linked together. Those objects are not physical storage structures but, e.g. Command Handlers and Query Handlers. They’re not related to where and how the data will be stored. They’re connected to processing behaviour.</p> <p>Command Handlers are responsible for handling commands, mutating state or doing other side effects.</p> <p>Query handlers are responsible for returning the result of the requested query.</p> <p><strong>Nothing prevents the Write Model and the Read Model from having the same structure or using the same tables.</strong> What’s more, CQRS doesn’t have to use any database. You can have Excel running behind the scene. You can query an external API. You can store data in a text file. Both CQS and CQRS do not tell anything about the end storage. The most important thing is to conceptually think differently about models for writing and reading data and not mixing them together.</p> <p><strong>Nothing then stops you from using a relational database, even the same table for reading and writing. If you prefer, you can continue to use ORM.</strong> Nothing prevents the command from adding/modifying records and query retrieving data from the same table. As long as we keep them separated in our architecture. Even on that level, you can benefit from CQRS, e.g. disable change tracking in query handlers, knowing that the query won’t change the state.</p> <p>Jimmy Bogard, in his article <a href="https://jimmybogard.com/vertical-slice-architecture/">“Vertical slice architecture”</a>, compared traditional “Clean Architecture” (or I should say <em>“Onion Architecture”</em>):</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 772px; height: auto" > <a class="gatsby-resp-image-link" href="/static/07b2e4403c83a8b377ad14ab3589044c/37601/cleanArchitecture.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 73.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAIDBf/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAHSaTk1EGv/xAAZEAADAQEBAAAAAAAAAAAAAAAAAQIhEDH/2gAIAQEAAQUCdCfKwnCfP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABUQAQEAAAAAAAAAAAAAAAAAACAh/9oACAEBAAY/Aov/xAAbEAADAAIDAAAAAAAAAAAAAAAAAREhQTGB0f/aAAgBAQABPyFj8C9qlvCZlqvRsbY1yP/aAAwDAQACAAMAAAAQ/M//xAAYEQACAwAAAAAAAAAAAAAAAAAAASGhsf/aAAgBAwEBPxDaHB//xAAXEQADAQAAAAAAAAAAAAAAAAAQESFh/9oACAECAQE/EK8H/8QAGxABAAMAAwEAAAAAAAAAAAAAAQARITFRYUH/2gAIAQEAAT8QaQeBye2VnQaJ9I5ICBvkXmBOzYQli4A8HsWxK28z/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cleanArchitecture.jpg" title="cleanArchitecture.jpg" src="/static/07b2e4403c83a8b377ad14ab3589044c/37601/cleanArchitecture.jpg" srcset="/static/07b2e4403c83a8b377ad14ab3589044c/37402/cleanArchitecture.jpg 200w, /static/07b2e4403c83a8b377ad14ab3589044c/4cda9/cleanArchitecture.jpg 400w, /static/07b2e4403c83a8b377ad14ab3589044c/37601/cleanArchitecture.jpg 772w" sizes="(max-width: 772px) 100vw, 772px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>to “Slice architecture”:</p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 581px; height: auto" > <a class="gatsby-resp-image-link" href="/static/7acdc75ab637432b6e5c6d2c8f61ffb4/daafc/sliceArchitecture.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 64.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAANCAYAAACpUE5eAAAACXBIWXMAABcRAAAXEQHKJvM/AAADL0lEQVQ4y23J3VNSeRzH8fOvdLPbTK5dMHXT1JLIwTggEqvnHDyg8qQidJBMBYSDbj5MoaIkT4PZRrsXbbnbMlMhAlphts52V+1e7NXONtouIqgdJ5Vv07EMnS5e8/79Pl/kxQB6Pz9WAa89FdkeCy082vzLV2hb6Bu+MVLGN05yjtVPlJ3QBMsq2j78I19yjG+cLP+2JXwEifUbAMJniv+OVYPKdRswJgkyJg7VzMw+7fA8kAMpkDrjICvZP4vvypgEYPZYBokN6N9B8DT845UWle67G2jvowLWm1wX96YL4r504UM1vsUCfvVJocqdKmDcljpsTdyXLop6Hs4gL/vPLWwHhZD1CvODOoNJT7oUtKqbNCm7iBaZiaOqaiE0mJForTETredpwkzZCBNl4/rxjZspG2VUdgqQV51nt3fCAsh6BMV5hfDtnES0OSdBN+fl2OYzQ+Pmoobaq1bFydTXcvc5qajU+rxUBHFxZRJ5eUmwU5zWQ3a8upiuEW0lxVI2iWFsukbOLlk72OfddvZ3M80utV9klyxW9qnewCbFGJvEJKU2UhIJPBCdm0X+Hjqb2Yl1wapfmr9Fn7aM01V40IKq/OZKyqs5SXnUxylPw3Hqqrqcq09/igpYUMpPH1AfpFG1t71ShjwPVP0PPzbBG79odzKmyE+kyNVAgsj5E3guMteUu7VA524+actFM2ZOYJbMBWaJA/wJIhdKKVc9d+VryB/+ygL4BPBmQlic+u08BBN1EI7XQSj+HUwl1XBnoQN+XrDC9GIn3M60Q2imFsKHhOK1EJnFYeSODJC/rqCPtwMoZMeEea8TN1ndzQoHoyVtbh3e5WrErQ4Sb74gx1tpOd7hVOI2RvcFWtzO6MkuRi1FHvdQWxDmw/IItjuoufafQ/3Tsqvh5oqzIbrPZ4uteC/dW3FQB/d96uhrV0N0o1t5/VfkUY8aIMIvLo9iMGTwgVMbBbduChjdDWC0N7j63fdg3DENzqbr4NbtbYe5dT+AvTG8iPx5GY2v+wSwPCxYHW3SS+qx8XKT4vsTrfIB3id1p7p55Bk7r00xyGuR9/NKbyVONtdc/vo927tAxRlIP7YAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="sliceArchitecture.png" title="sliceArchitecture.png" src="/static/7acdc75ab637432b6e5c6d2c8f61ffb4/daafc/sliceArchitecture.png" srcset="/static/7acdc75ab637432b6e5c6d2c8f61ffb4/36ca5/sliceArchitecture.png 200w, /static/7acdc75ab637432b6e5c6d2c8f61ffb4/a3397/sliceArchitecture.png 400w, /static/7acdc75ab637432b6e5c6d2c8f61ffb4/daafc/sliceArchitecture.png 581w" sizes="(max-width: 581px) 100vw, 581px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>In CQRS, you’re also cutting your model and API into vertical slices. Each command/query handler is a separate silo. This is where you can reduce coupling between layers. Each handler can be a separated code unit, even copy/pasted. Thanks to that, you can tune down the specific method to not follow general conventions (e.g. use custom SQL query or even different storage). In a traditional layered architecture, when you change the core generic mechanism in one layer, it can impact all methods. This is a huge risk that may lead to abandoning the needed change. Having a vertical split also helps developer to focus on a particular business feature. The world is not perfect: sometimes there are more exceptions than cases following the rules. It’s good to have the option to differentiate our solution as the situation calls for it.</p> <p>Btw. comparing Jimmy’s diagrams I have doubts as to why CQRS is perceived as the complicated one.</p> <p><strong>Why do people think that you need to have different tables or databases in CQRS?</strong></p> <p>CQRS allows for the refinement of the query model to suit the clients needs. It’s a typical case to have a dedicated read model for each view. You don’t need all the details of the records while you’re just displaying a short summary. Such read models can be slightly different SQL queries selecting a different range of columns from the table. But they can also be materialised views or separate tables. You may do that for the performance optimisation; to not have writes and queries impacting each other. If you have such a case, then one of the potential solutions is to use different tables or databases and synchronise them after writing. However, this is not a general rule. You should select your strategy that suits your needs. I always suggest starting small before growing big.</p> <p><strong>Where did the need for messaging queues come from?</strong></p> <p>CQRS allows you to have different storages for different business cases. You can, for example, have a relational database on the write model and document database on the read model (e.g. Elastic Search for full-text search) or any other combination. Creating a read model also needs to have transformation logic. It’s not one-to-one data mapping. You can have separate services responsible for the business logic modifying state and rebuilding read models for such needs. Messaging queues (e.g. RabbitMQ, Kafka) can help synchronise them by notifying the read model processors about the new change in the write model. Again, you can start smaller with the tables and database views, in-memory queue or CDC. It all depends on your scenario, <a href="https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">guarantees you need to have</a> etc.</p> <p><strong>What about Eventual Consistency in the CQRS?</strong></p> <p>If you’re using messaging queues or multiple databases, then the state coordination nature is usually asynchronous. Even materialised views or replication in the database can be eventually consistent.</p> <p>When a change made in the write-model affects multiple read models in the same transaction, it’s worth considering if the performance’s impact is significant. One potential approach is to offload the update into the background, asynchronous process. Knowing what changes were made, you can process them one by one or, e.g. in the batch <a href="https://en.wikipedia.org/wiki/Extract,_transform,_load">ETL</a> process.</p> <p>Read more on that in my post about <a href="https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">the Outbox Pattern</a>.</p> <p><strong>Do you need Event Sourcing?</strong></p> <p>No, you don’t. Event Sourcing is quite often conflated with CQRS. Event Sourcing, by definition, has Write and Read models. Events are stored in the append-only log. They are the source of truth. Read models are created and updated based on events. Event Sourcing and CQRS matches like tequila to salt and lime. However, they are different patterns. Event Sourcing is <em>“just”</em> one of the options you can choose as a storage implementation. You don’t have to use it if you don’t want to.</p> <p><strong>To complete the tackle with CQ(R)S myths, check the following links:</strong></p> <ul> <li><a href="https://cqrs.files.wordpress.com/2010/11/cqrs_documents.pdf">Greg Young - CQRS Documents</a></li> <li><a href="http://laser.inf.ethz.ch/2012/slides/Meyer/eiffel_laser_2012.pdf">Bertrand Meyer - Eiffel: a language for software engineering</a></li> <li><a href="https://martinfowler.com/bliki/CQRS.html">Martin Fowler - CQRS</a></li> <li><a href="https://udidahan.com/2009/12/09/clarified-cqrs">Udi Dahan - Clarified CQRS</a></li> <li><a href="https://jimmybogard.com/vertical-slice-architecture/">Jimmy Bogard - Vertical slice architecture</a></li> <li><a href="https://www.youtube.com/watch?v=3gib0hKYjB0">Kent Beck talk about coupling and cohesion</a></li> </ul> <p>See also my repository, where I’ve gathered practical samples and other resources about CQRS and Event Sourcing: <a href="https://github.com/oskardudycz/EventSourcing.NetCore">https://github.com/oskardudycz/EventSourcing.NetCore</a>.</p> <p>It’s always best to read the sources and verify if things told on the Internet are truth or Urban Legends. Too often, we make our decisions based on anecdotal evidence rather than research.</p> <p>I hope that this helps you to sift the wheat from the chaff.</p> <p>If you liked this article, also check others where I’m trying to bust similar myths:</p> <ul> <li><a href="/en/event_streaming_is_not_event_sourcing/">Event Streaming is not Event Sourcing!</a></li> <li><a href="/en/dont_let_event_driven_architecture_buzzwords_fool_you/">Don’t let Event-Driven Architecture buzzwords fool you</a></li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[Why Partial<Type> is an extremely useful TypeScript feature?]]>https://event-driven.io/en/partial_typescript/https://event-driven.io/en/partial_typescript/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/de01ccfa64d9196122614ab91aa5a80b/d2429/2021-02-24-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AEKPmDyBkUeZnFScnkyGlEWFkFCTmVaKmmGCn2eCo2V+pGuEplxzmFWCmU6UmkN7jUh9j0N6jTt/kD1/kAA0dYcxboU5fIeltbierLCqsbPDxsfFwseorbGutLagrrCvtLW8vb+5ub+usrS7wsK4vsBtkpwmZH03eooALml8LF55I1Zzlqm0i5WmrLLAqre7r8G/sLq5tL6/xsvJrrayyM7Qy9LVy83UmJyvoKOzYI2YIFZ0LWJ6AB9SbR9TbRtPaWVzjl5rk1V6jJmrY8rBXM+qasKacLWKesOcf7qWbsKzdrS6eExud11olUJdexZGah9SbQAqU2knU2k1UXRJUocxZHZcgWLFozfrmhronCzkskjivBLitiflnBnypSPoxR+juTw3XHtCTX4rVGkqVWoAfpmGfpR/eXyFX4KQlrRqwp9myYI62rQwxMa1sLnDzdGcwsm43uGx5Lon6JYm3LoolLBPa3qfgoyLf5uDAIuimJigVLCtH5CrYcGlVrF/bb23P+LgPbi9x5ictMnK3oKIpO/u+OboPt+5G8yAKMW+RG2UjXZ6pZCllwCpv66svJHTzyvb1Bm5qGGktrXL1Fnj2Q3V0Efj34rZ1Wjt8MXg13bdzxrX0STEjijMl0+ht3eMklacpH0AeHNUf31okoxEu7QdkJFti49/w8FB7eUUyMMd0M0l4uAcvqYbmWwR4dUavrIlu64+wKtTz8Mv0cwUtb13ACUYCCQWBzAjDnt1FjAjB5qYk9bUgNjRDt7ZKKSEeMKjnKVRTbWTH9/dH4yGKI2ALGtiJd7ZFY+HJ3RsVAAuIxItIhIvJBGRiReQiRCtrYbQzGqmoyHj3BnAmCTLnErr1S/Gxiayqh7PxWe9vcNSSi6SixIoHBAkFwisHF+OmaUcZwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/de01ccfa64d9196122614ab91aa5a80b/a331c/2021-02-24-cover.png" srcset="/static/de01ccfa64d9196122614ab91aa5a80b/36ca5/2021-02-24-cover.png 200w, /static/de01ccfa64d9196122614ab91aa5a80b/a3397/2021-02-24-cover.png 400w, /static/de01ccfa64d9196122614ab91aa5a80b/a331c/2021-02-24-cover.png 800w, /static/de01ccfa64d9196122614ab91aa5a80b/d2429/2021-02-24-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>If TypeScript were a friend of mine on Facebook, then I’d mark our relation as complicated. It’s a history of love &#x26; hate, or rather hate &#x26; appreciation. <strong>I was a TypeScript hater.</strong> It was lying in the same bucket as CoffeeScript, Dart and other weird variations of JavaScript.</p> <p>That’s not all. As befits a C# developer, I was also a JavaScript hater. With JavaScript, relation quickly evolved into a friendship as soon as I realised that’s powerful when I use it in a functional way. I realised that I tried to apply the same coding patterns as in C#. JavaScript is a dynamic and more functional language. It was a recipe for disaster.</p> <p>That’s why I wasn’t a TypeScript fan. Its early versions were adding too many incompatible to JavaScript specifics. TypeScript was, for me, the Troyan Horse made by imperative, compiled languages programmers.</p> <p><strong>That changed when TypeScript was aligned to EcmaScript 6 standard.</strong> I also read some eye-opening content about Type-Driven Development, e.g. <a href="https://pragprog.com/titles/swdddf/domain-modeling-made-functional/">“Domain Modeling Made Functional”</a> by Scott Wlaschin. I realised that I could use TypeScript the same way I was using JavaScript but add more predictability and reliability related to types.</p> <p>This week I was preparing the <a href="https://www.eventstore.com/blog/nodejs-v1-release">blog post</a> about the v1 release of <a href="https://developers.eventstore.com/clients/grpc/getting-started/?codeLanguage=NodeJS">EventStoreDB gRPC NodeJS client</a>. While working on the samples, I started to play with our API and building the current aggregate state from events (<em>“aggregate stream”</em>). NodeJS client is built with TypeScript, so why not?</p> <p>I started by defining event types and aggregate data. I used the cinema ticket reservation as the sample use case.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">interface</span> <span class="token class-name">SeatReserved</span> <span class="token punctuation">{</span> eventType<span class="token operator">:</span> <span class="token string">'SeatReserved'</span><span class="token punctuation">;</span> reservationId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> movieId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> seatId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> userId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">interface</span> <span class="token class-name">SeatChanged</span> <span class="token punctuation">{</span> eventType<span class="token operator">:</span> <span class="token string">'SeatChanged'</span><span class="token punctuation">;</span> reservationId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> newSeatId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">type</span> <span class="token class-name">ReservationEvents</span> <span class="token operator">=</span> SeatReserved <span class="token operator">|</span> SeatChanged<span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">Reservation</span> <span class="token punctuation">{</span> reservationId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> movieId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> seatId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> userId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>As you see - nothing extraordinary. You can reserve the seat and change it. The important part is that both events have the <em>eventType</em> property with a hardcoded event type name. We’ll use it later.</p> <p><strong>In Event Sourcing, events are logically grouped into streams.</strong> Streams are a representation of entities. Each business operation made on the entity should end up as the persisted event.</p> <p><strong>Entity state is retrieved by reading all events and applying them one by one in the order of appearance.</strong> We’re translating the set of events into a single entity. This is what’s the <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce">reduce</a> function was built for. It executes a reducer function (that you can provide) on each array element, resulting in a single output value.</p> <p>This is cool, but how can we do it with proper typing and not taking shortcuts with casting?</p> <p><strong>There are 3 things to cover:</strong></p> <ol> <li><em>reduce</em> in TypeScript is a generic method. It allows to provide the result type as a parameter. It doesn’t have to be the same as type of the array elements.</li> <li>You can also use optional param to provide the default value for accumulation.</li> <li>Use <a href="https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype">Partial&#x3C;Type></a> as the generic reduce param. It constructs a type with all properties of Type set to optional. This utility will return a type that represents all subsets of a given type. This is extremely important, as TypeScript forces you to define all required properties. We’ll be merging different states of the aggregate state into the final one. Only the first event (<em>SeatReserved</em>) will provide all required fields. The other events will just do a partial update (<em>SeatChanged</em> only changes the <em>seatId</em>).</li> </ol> <p>Let’s see how it works in practice:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">var</span> events<span class="token operator">:</span> ReservationEvents<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token punctuation">{</span> eventType<span class="token operator">:</span> <span class="token string">'SeatReserved'</span><span class="token punctuation">,</span> reservationId<span class="token operator">:</span> <span class="token string">'res-homeAlone-1'</span><span class="token punctuation">,</span> movieId<span class="token operator">:</span> <span class="token string">'homeAlone'</span><span class="token punctuation">,</span> seatId<span class="token operator">:</span> <span class="token string">'44'</span><span class="token punctuation">,</span> userId<span class="token operator">:</span> <span class="token string">'ms_smith'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> eventType<span class="token operator">:</span> <span class="token string">'SeatChanged'</span><span class="token punctuation">,</span> reservationId<span class="token operator">:</span> <span class="token string">'res-homeAlone-1'</span><span class="token punctuation">,</span> newSeatId<span class="token operator">:</span> <span class="token string">'21'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">const</span> result <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">reduce</span><span class="token generic class-name"><span class="token operator">&lt;</span>Partial<span class="token operator">&lt;</span>Reservation<span class="token operator">>></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>currentState<span class="token punctuation">,</span> event<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token punctuation">.</span>eventType<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">'SeatReserved'</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> reservationId<span class="token operator">:</span> event<span class="token punctuation">.</span>reservationId<span class="token punctuation">,</span> movieId<span class="token operator">:</span> event<span class="token punctuation">.</span>movieId<span class="token punctuation">,</span> seatId<span class="token operator">:</span> event<span class="token punctuation">.</span>seatId<span class="token punctuation">,</span> userId<span class="token operator">:</span> event<span class="token punctuation">.</span>userId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">'SeatChanged'</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>currentState<span class="token punctuation">,</span> seatId<span class="token operator">:</span> event<span class="token punctuation">.</span>newSeatId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">throw</span> <span class="token string">'Unexpected event type'</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre></div> <p>Thanks to strong typing (<em>ReservationEvents</em>), we’re sure about the events array’s content. We know that both events will have the <em>eventType</em> property. Having that, we can use <em>switch</em> and define a custom state mutation logic for each event.</p> <p>The only thing left is to make sure that our final result has a proper state and can be used as the <em>Reservation</em> type. Remember, the result of <em>reduce</em> will be <em>Partial<Reservation></em> with all required fields made optional. We can use <a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types">type guard</a> to verify if <em>Partial<Reservation></em> is also a valid <em>Reservation</em>.</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">const</span> reservationIsValid <span class="token operator">=</span> <span class="token punctuation">(</span>reservation<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>Reservation<span class="token operator">></span><span class="token punctuation">)</span><span class="token operator">:</span> reservation <span class="token keyword">is</span> Reservation <span class="token operator">=></span> <span class="token punctuation">(</span> <span class="token operator">!</span><span class="token operator">!</span>reservation<span class="token punctuation">.</span>reservationId <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token operator">!</span>reservation<span class="token punctuation">.</span>movieId <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token operator">!</span>reservation<span class="token punctuation">.</span>seatId <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token operator">!</span>reservation<span class="token punctuation">.</span>userId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">reservationIsValid</span><span class="token punctuation">(</span>reservation<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token string">"Reservation state is not valid!"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> reservation<span class="token operator">:</span> Reservation <span class="token operator">=</span> result<span class="token punctuation">;</span></code></pre></div> <p>As a bonus, let me present you the full working sample with the <a href="https://developers.eventstore.com/clients/grpc/getting-started/?codeLanguage=NodeJS">EventStoreDB NodeJS gRPC client</a>:</p> <div class="gatsby-highlight" data-language="typescript"><pre class="language-typescript"><code class="language-typescript"><span class="token keyword">import</span> <span class="token punctuation">{</span> EventStoreDBClient<span class="token punctuation">,</span> JSONEventType<span class="token punctuation">,</span> jsonEvent <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">"@eventstore/db-client"</span><span class="token punctuation">;</span> <span class="token comment">// define types</span> <span class="token keyword">type</span> <span class="token class-name">SeatReserved</span> <span class="token operator">=</span> JSONEventType<span class="token operator">&lt;</span> <span class="token string">"SeatReserved"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> reservationId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> movieId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> userId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> seatId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">SeatChanged</span> <span class="token operator">=</span> JSONEventType<span class="token operator">&lt;</span> <span class="token string">"SeatChanged"</span><span class="token punctuation">,</span> <span class="token punctuation">{</span> reservationId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> newSeatId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">></span><span class="token punctuation">;</span> <span class="token keyword">type</span> <span class="token class-name">ReservationEvents</span> <span class="token operator">=</span> SeatReserved <span class="token operator">|</span> SeatChanged<span class="token punctuation">;</span> <span class="token keyword">interface</span> <span class="token class-name">Reservation</span> <span class="token punctuation">{</span> reservationId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> movieId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> userId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> seatId<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">const</span> reservationIsValid <span class="token operator">=</span> <span class="token punctuation">(</span>reservation<span class="token operator">:</span> Partial<span class="token operator">&lt;</span>Reservation<span class="token operator">></span><span class="token punctuation">)</span><span class="token operator">:</span> reservation <span class="token keyword">is</span> Reservation <span class="token operator">=></span> <span class="token punctuation">(</span> <span class="token operator">!</span><span class="token operator">!</span>reservation<span class="token punctuation">.</span>reservationId <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token operator">!</span>reservation<span class="token punctuation">.</span>movieId <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token operator">!</span>reservation<span class="token punctuation">.</span>seatId <span class="token operator">&amp;&amp;</span> <span class="token operator">!</span><span class="token operator">!</span>reservation<span class="token punctuation">.</span>userId <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// create events</span> <span class="token keyword">const</span> reservationId <span class="token operator">=</span> <span class="token string">"res-homeAlone-1"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> seatReserved <span class="token operator">=</span> <span class="token generic-function"><span class="token function">jsonEvent</span><span class="token generic class-name"><span class="token operator">&lt;</span>SeatReserved<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"SeatReserved"</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> reservationId<span class="token punctuation">,</span> movieId<span class="token operator">:</span> <span class="token string">"homeAlone"</span><span class="token punctuation">,</span> userId<span class="token operator">:</span> <span class="token string">"ms_smith"</span><span class="token punctuation">,</span> seatId<span class="token operator">:</span> <span class="token string">"44"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> seatChanged <span class="token operator">=</span> <span class="token generic-function"><span class="token function">jsonEvent</span><span class="token generic class-name"><span class="token operator">&lt;</span>SeatChanged<span class="token operator">></span></span></span><span class="token punctuation">(</span><span class="token punctuation">{</span> type<span class="token operator">:</span> <span class="token string">"SeatChanged"</span><span class="token punctuation">,</span> data<span class="token operator">:</span> <span class="token punctuation">{</span> reservationId<span class="token punctuation">,</span> newSeatId<span class="token operator">:</span> <span class="token string">'21'</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// connect to EventStoreDB</span> <span class="token keyword">const</span> client <span class="token operator">=</span> EventStoreDBClient<span class="token punctuation">.</span><span class="token function">connectionString</span><span class="token punctuation">(</span><span class="token string">"esdb://localhost:2113?tls=false"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// append events</span> <span class="token keyword">const</span> appendResult <span class="token operator">=</span> <span class="token keyword">await</span> client<span class="token punctuation">.</span><span class="token function">appendToStream</span><span class="token punctuation">(</span> reservationId<span class="token punctuation">,</span> seatReserved<span class="token punctuation">,</span> seatChanged<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// read appended events</span> <span class="token keyword">const</span> events<span class="token operator">:</span> ReservationEvents<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token keyword">for</span> <span class="token keyword">await</span> <span class="token punctuation">(</span><span class="token keyword">const</span> resolvedEvent <span class="token keyword">of</span> eventStore<span class="token punctuation">.</span><span class="token function">readStream</span><span class="token punctuation">(</span> reservationId <span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> events<span class="token punctuation">.</span><span class="token function">push</span><span class="token punctuation">(</span><span class="token operator">&lt;</span>ReservationEvents<span class="token operator">></span><span class="token punctuation">{</span> type<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">!</span><span class="token punctuation">.</span>type<span class="token punctuation">,</span> data<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">!</span><span class="token punctuation">.</span>data<span class="token punctuation">,</span> metadata<span class="token operator">:</span> resolvedEvent<span class="token punctuation">.</span>event<span class="token operator">?.</span>metadata<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// aggregate stream</span> <span class="token keyword">const</span> result <span class="token operator">=</span> events<span class="token punctuation">.</span><span class="token generic-function"><span class="token function">reduce</span><span class="token generic class-name"><span class="token operator">&lt;</span>Partial<span class="token operator">&lt;</span>Reservation<span class="token operator">>></span></span></span><span class="token punctuation">(</span><span class="token punctuation">(</span>acc<span class="token punctuation">,</span> <span class="token punctuation">{</span> event <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span>event<span class="token operator">?.</span>type<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token string">"SeatReserved"</span><span class="token operator">:</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>acc<span class="token punctuation">,</span> reservationId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>reservationId<span class="token punctuation">,</span> movieId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>movieId<span class="token punctuation">,</span> seatId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>seatId<span class="token punctuation">,</span> userId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>userId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token keyword">case</span> <span class="token string">"SeatChanged"</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">{</span> <span class="token operator">...</span>acc<span class="token punctuation">,</span> seatId<span class="token operator">:</span> event<span class="token punctuation">.</span>data<span class="token punctuation">.</span>newSeatId<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">return</span> acc<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span><span class="token punctuation">(</span><span class="token operator">!</span><span class="token function">reservationIsValid</span><span class="token punctuation">(</span>result<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token string">"Reservation state is not valid!"</span><span class="token punctuation">;</span> <span class="token keyword">const</span> reservation<span class="token operator">:</span> Reservation <span class="token operator">=</span> result<span class="token punctuation">;</span></code></pre></div> <p>I wrote a longer take on <a href="/pl/how_to_get_the_current_entity_state_in_event_sourcing/">“How to get the current entity state from events?”</a>. If you want to see how to use that pattern to build a whole NodeJS WebApi read my other article <a href="/pl/type_script_node_Js_event_sourcing">Straightforward Event Sourcing with TypeScript and NodeJS</a>.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[How to set up a test matrix in XUnit?]]>https://event-driven.io/en/how_to_setup_a_test_matrix_in_xunit/https://event-driven.io/en/how_to_setup_a_test_matrix_in_xunit/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/da2e5d520fba8a041b8b7dbdef3afff4/d2429/2021-02-21-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACnklEQVQozwXBWVMSAQAA4O3BaTCnBzVzlEE55BCHYzhCQYlDbtjlxuVYWJZLbpFDEFTAIE1FJAtNGbPGykmrl2b6dX0f0DlGUxl3JO5OZT25kr9YCfb7ybvvxe55try73nib3mpkm4e5017xXbfS+VD9eNXoXR70B53zqxMAixkQDIRRs9NvC0btkbhjq7r28OC/uAyEUr54Do1mgokcmi4Ea/VQoxVptNYre/F6K9toZYAgKoxFFoOYJIApfQGNFwOTBbB7YXpzYsvtwRs1OJF3hVIONObyRVxYEs6VfOkCki4gmQICaI0sPcg2mbkYyvX4hE7PSiTxuvdJli/Ja0e604ExX9OaYe1GwRRNQkgYCiegaMoaSzvCCQfAF89LFCzh8oIRYtlcCxoTT28VhtOiWFZca670b5SlxqrFJ/OGlMGIyh9SO/0qC6yzenR2rwHgiChsIZUrmpepaXqIroeYEhXLYON4w/xERrS7zd+q8tM1qdO/ZFsT623LGuuK0iQzOldBuxygMEfo7HGpYlYgofHEVK2eBIIUmZoZTDMQdH7NMgHpRwOBWaeH4/TywxuL0awgsU4NYdRseQmg0HA0Bo4neCaVTem0eIMBb7VMqdXTSHTGbp+RS58zmMNsFs4IEnQQzYuQAvCYRjmsUuA28wRgcuoJeW4IT3i6tDgkkYwwOWMs/ksSY3RVM2HT4V8JxmmMCYFw0mImKeSzNmguBBMsBgJkIGIeIrBfdNQS8gwi6reRbt19UnVdt323x2gKVfNo462UbNCCL2qGLwf2+47733Xk53vsW1v7eAzeH4LATV3wq8c9rbzobS+clUnX+8T7I/LvM1qnhN/0vvy8M31bZw7KlMfm9N8jxp9D+tcd8l2T/qNNvNkn/wd6n+ehst3F6wAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/da2e5d520fba8a041b8b7dbdef3afff4/a331c/2021-02-21-cover.png" srcset="/static/da2e5d520fba8a041b8b7dbdef3afff4/36ca5/2021-02-21-cover.png 200w, /static/da2e5d520fba8a041b8b7dbdef3afff4/a3397/2021-02-21-cover.png 400w, /static/da2e5d520fba8a041b8b7dbdef3afff4/a331c/2021-02-21-cover.png 800w, /static/da2e5d520fba8a041b8b7dbdef3afff4/d2429/2021-02-21-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Each country has the go-to place for hiding from daily struggles. In Poland, we have <a href="https://www.youtube.com/watch?v=wea2dvx0pEU">Bieszczady</a>. It’s a mountain range that’s also the wildest part of our country. You will find there: solitude, forests, wolves, bobcats and all that wild nature stuff. Beautiful place with a mysterious vibe similar to Twin Peaks. We have a running joke in Poland, <em>“Fuck that! Let’s go breed sheep in Bieszczady!”</em>. That was precisely the feeling I had while adding <a href="https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-overview">System.Text.Json</a> support to <a href="https://martendb.io/">Marten</a>.</p> <p>If you’re <a href="https://twitter.com/oskar_at_net">following me on Twitter</a>, then you probably saw a few of my rants about that. I’ll spare you my frustration and summarise: it was an unpleasant surprise. STJ is not covering many basic scenarios, and its design decisions are at least debatable (e.g. the need to use attributes everywhere).</p> <p><strong>Still, we decided to support System.Text.Json, as:</strong></p> <ul> <li>the limited feature-set may not hit users that need only simple <a href="https://en.wikipedia.org/wiki/Plain_old_CLR_object">POCO</a>,</li> <li>it may give performance improvement,</li> <li>Newtonsoft Json.NET seems not to be supported enough and might get obsolete soon.</li> </ul> <p>Adding a new serialiser to the library like Marten is a challenge. <strong>Marten, thanks to <a href="https://scalegrid.io/blog/using-jsonb-in-postgresql-how-to-effectively-store-index-json-data-in-postgresql/">Postgres JSON capabilities</a> uses it as a document database and an event store.</strong> JSON serialisation and translation is a centrepiece of our actions.</p> <p>We have a big set of unit and integration tests (a few thousand) to ensure that all edge cases are covered. We are taking the free CI/CD engines spinning to their limits.</p> <ul> <li>GitHub Actions - to run against .NET Core 3.1,</li> <li>AzureDevops - for .NET 5.</li> </ul> <p>Both environments are running build matrix tests for Postgres versions ranging from 9.6 to 12. That’s a hellova test runs and permutations.</p> <p>As I mentioned, JSON serialisation and mapping (e.g., constructing SQL queries from LINQ) are critical in Marten. I could take a similar approach as I did in adding NodaTime support - dedicated tests. However, I wanted to make sure that we can precisely know what’s working and what’s not. That’s why I decided to add matrix tests to run for both serialisers.</p> <p>That appeared harder than I thought. <strong>Both AzureDevops and GitHub Actions have decent support for the build matrix. However, this approach is against the XUnit conventions.</strong> It’s recommended to have explicit test data. It is a reasonable approach, but as I described, sometimes you have bigger needs. Tuning a framework to your needs is always hard. It took me a lot of googling, and a few tries to achieve that. That’s why I decided to share the solution with you.</p> <p>In the samples below, I’ll be using, for clarity, sample project: <a href="https://github.com/oskardudycz/XUnit.MatrixTests/">https://github.com/oskardudycz/XUnit.MatrixTests/</a>. I extracted part responsible for setting up the matrix tests with XUnit.</p> <p>If you prefer, you can check the full Marten’s Pull Request: <a href="https://github.com/JasperFx/marten/pull/1685">https://github.com/JasperFx/marten/pull/1685</a>.</p> <p><strong>The first case to solve was passing information about the selected serialiser type for the test suite run.</strong> XUnit doesn’t support any test settings file. That’s why I followed KISS and used a good old environment variable. Based on it, I’m setting the default serialiser. To do that, I created two classes:</p> <p>Factory to be able to select default serialiser:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">enum</span> <span class="token class-name">SerializerType</span> <span class="token punctuation">{</span> NewtonsoftJsonNet <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">,</span> SystemTextJson <span class="token operator">=</span> <span class="token number">2</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">SerializerFactory</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">SerializerType</span> DefaultSerializerType <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token operator">=</span> SerializerType<span class="token punctuation">.</span>NewtonsoftJsonNet<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">ISerializer</span> <span class="token function">New</span><span class="token punctuation">(</span><span class="token class-name">SerializerType<span class="token punctuation">?</span></span> serializerType <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> serializerType <span class="token operator">??=</span> DefaultSerializerType<span class="token punctuation">;</span> <span class="token keyword">return</span> serializerType <span class="token keyword">switch</span> <span class="token punctuation">{</span> SerializerType<span class="token punctuation">.</span>NewtonsoftJsonNet <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">NewtonsoftSerializer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> SerializerType<span class="token punctuation">.</span>SystemTextJson <span class="token operator">=></span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SystemTextJsonSerializer</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> _ <span class="token operator">=></span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">ArgumentOutOfRangeException</span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>serializerType<span class="token punctuation">)</span><span class="token punctuation">,</span> serializerType<span class="token punctuation">,</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">}</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Test setting class that, based on the environment variable, gets the serialiser type.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">TestsSettings</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">static</span> <span class="token class-name">SerializerType<span class="token punctuation">?</span></span> serializerType<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token return-type class-name">SerializerType</span> SerializerType <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>serializerType<span class="token punctuation">.</span>HasValue<span class="token punctuation">)</span> <span class="token keyword">return</span> serializerType<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> defaultSerializerEnv <span class="token operator">=</span> Environment<span class="token punctuation">.</span><span class="token function">GetEnvironmentVariable</span><span class="token punctuation">(</span><span class="token string">"DEFAULT_SERIALIZER"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> serializerType <span class="token operator">=</span> Enum<span class="token punctuation">.</span>TryParse<span class="token class-name"><span class="token punctuation">(</span>defaultSerializerEnv<span class="token punctuation">,</span> <span class="token keyword">out</span> SerializerType parsedSerializerType<span class="token punctuation">)</span> <span class="token punctuation">?</span></span> parsedSerializerType <span class="token punctuation">:</span> SerializerType<span class="token punctuation">.</span>NewtonsoftJsonNet<span class="token punctuation">;</span> <span class="token keyword">return</span> serializerType<span class="token punctuation">.</span>Value<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Having those classes, the only issue left was initialising the test settings automatically before the XUnit test run. It’s possible by creating your own <strong>XunitTestFramework</strong>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">using</span> <span class="token namespace">Weasel<span class="token punctuation">.</span>Serialization</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Xunit</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Xunit<span class="token punctuation">.</span>Abstractions</span><span class="token punctuation">;</span> <span class="token keyword">using</span> <span class="token namespace">Xunit<span class="token punctuation">.</span>Sdk</span><span class="token punctuation">;</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token target keyword">assembly</span><span class="token punctuation">:</span> <span class="token class-name">TestFramework</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"XUnit.MatrixTests.TestSetup"</span><span class="token punctuation">,</span> <span class="token string">"XUnit.MatrixTests"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">namespace</span> <span class="token namespace">XUnit<span class="token punctuation">.</span>MatrixTests</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">class</span> <span class="token class-name">TestSetup</span> <span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">XunitTestFramework</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token function">TestSetup</span><span class="token punctuation">(</span><span class="token class-name">IMessageSink</span> messageSink<span class="token punctuation">)</span> <span class="token punctuation">:</span><span class="token keyword">base</span><span class="token punctuation">(</span>messageSink<span class="token punctuation">)</span> <span class="token punctuation">{</span> SerializerFactory<span class="token punctuation">.</span>DefaultSerializerType <span class="token operator">=</span> TestsSettings<span class="token punctuation">.</span>SerializerType<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">new</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Place tear down code here</span> <span class="token keyword">base</span><span class="token punctuation">.</span><span class="token function">Dispose</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>You can do a lot of customisation there, but for our case, it was enough to connect the dots and set the default serialiser type. Besides deriving from <strong>XunitTestFramework</strong> it’s also needed to put the assembly attribute. Beware - you need to use the magical strings with the name of your tests project assembly and defined test framework name with a namespace. The wrong copy&#x26;paste can hurt a lot!</p> <p><strong>That’s all to have the XUnit build matrix!</strong></p> <p><strong>Well, almost.</strong> I knew already that some of the test scenarios wouldn’t work. System.Text.Json is not working correctly with the anonymous and dynamic types, classes hierarchies etc. <strong>I wanted to have an option to mark some tests as only supported for Newtonsoft.</strong> I used the pattern that we already had for skipping features unsupported in the older Postgres versions (e.g. full-text search below version 10). I implemented the custom <strong>XUnit Fact</strong> for that with information about the selected serialiser.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">AttributeUsage</span><span class="token attribute-arguments"><span class="token punctuation">(</span>AttributeTargets<span class="token punctuation">.</span>Method<span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">XunitTestCaseDiscoverer</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"XUnit.MatrixTests.Extras.SerializerTargetedFactDiscoverer"</span><span class="token punctuation">,</span> <span class="token string">"XUnit.MatrixTests"</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">SerializerTypeTargetedFact</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">FactAttribute</span></span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token return-type class-name">SerializerType</span> RunFor <span class="token punctuation">{</span> <span class="token keyword">get</span><span class="token punctuation">;</span> <span class="token keyword">set</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Besides that, I needed to implement a custom fact discoverer to skip the test if the serialiser used in the test run is different than selected in <strong>SerializerTypeTargetedFact</strong>.</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">SerializerTargetedFactDiscoverer</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">FactDiscoverer</span></span> <span class="token punctuation">{</span> <span class="token keyword">private</span> <span class="token keyword">readonly</span> <span class="token class-name">SerializerType</span> serializerType<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token function">SerializerTargetedFactDiscoverer</span><span class="token punctuation">(</span><span class="token class-name">IMessageSink</span> diagnosticMessageSink<span class="token punctuation">)</span><span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>diagnosticMessageSink<span class="token punctuation">)</span> <span class="token punctuation">{</span> serializerType <span class="token operator">=</span> TestsSettings<span class="token punctuation">.</span>SerializerType<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token keyword">override</span> <span class="token return-type class-name">IEnumerable<span class="token punctuation">&lt;</span>IXunitTestCase<span class="token punctuation">></span></span> <span class="token function">Discover</span><span class="token punctuation">(</span><span class="token class-name">ITestFrameworkDiscoveryOptions</span> discoveryOptions<span class="token punctuation">,</span> <span class="token class-name">ITestMethod</span> testMethod<span class="token punctuation">,</span> <span class="token class-name">IAttributeInfo</span> factAttribute<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> runForSerializer <span class="token operator">=</span> factAttribute<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">GetNamedArgument</span><span class="token generic class-name"><span class="token punctuation">&lt;</span>SerializerType<span class="token punctuation">?</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token keyword">nameof</span><span class="token punctuation">(</span>SerializerTypeTargetedFact<span class="token punctuation">.</span>RunFor<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>runForSerializer <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> runForSerializer <span class="token operator">!=</span> serializerType<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">TestCaseSkippedDueToSerializerSupport</span><span class="token punctuation">(</span><span class="token interpolation-string"><span class="token string">$"Test skipped as it cannot be run for </span><span class="token interpolation"><span class="token punctuation">{</span><span class="token expression language-csharp">serializerType</span><span class="token punctuation">}</span></span><span class="token string"> "</span></span><span class="token punctuation">,</span> DiagnosticMessageSink<span class="token punctuation">,</span> discoveryOptions<span class="token punctuation">.</span><span class="token function">MethodDisplayOrDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> discoveryOptions<span class="token punctuation">.</span><span class="token function">MethodDisplayOptionsOrDefault</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> testMethod<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">yield</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">yield</span> <span class="token keyword">return</span> <span class="token function">CreateTestCase</span><span class="token punctuation">(</span>discoveryOptions<span class="token punctuation">,</span> testMethod<span class="token punctuation">,</span> factAttribute<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">internal</span> <span class="token keyword">sealed</span> <span class="token keyword">class</span> <span class="token class-name">TestCaseSkippedDueToSerializerSupport</span><span class="token punctuation">:</span> <span class="token type-list"><span class="token class-name">XunitTestCase</span></span> <span class="token punctuation">{</span> <span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">Obsolete</span><span class="token attribute-arguments"><span class="token punctuation">(</span><span class="token string">"Called by the de-serializer"</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token function">TestCaseSkippedDueToSerializerSupport</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token function">TestCaseSkippedDueToSerializerSupport</span><span class="token punctuation">(</span><span class="token class-name"><span class="token keyword">string</span></span> skipReason<span class="token punctuation">,</span> <span class="token class-name">IMessageSink</span> diagnosticMessageSink<span class="token punctuation">,</span> <span class="token class-name">TestMethodDisplay</span> defaultMethodDisplay<span class="token punctuation">,</span> <span class="token class-name">TestMethodDisplayOptions</span> defaultMethodDisplayOptions<span class="token punctuation">,</span> <span class="token class-name">ITestMethod</span> testMethod<span class="token punctuation">,</span> <span class="token class-name"><span class="token keyword">object</span><span class="token punctuation">[</span><span class="token punctuation">]</span></span> testMethodArguments <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">base</span><span class="token punctuation">(</span>diagnosticMessageSink<span class="token punctuation">,</span> defaultMethodDisplay<span class="token punctuation">,</span> defaultMethodDisplayOptions<span class="token punctuation">,</span> testMethod<span class="token punctuation">,</span> testMethodArguments<span class="token punctuation">)</span> <span class="token punctuation">{</span> SkipReason <span class="token operator">=</span> skipReason<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>Then I could use it as:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token punctuation">[</span><span class="token attribute"><span class="token class-name">SerializerTypeTargetedFact</span><span class="token attribute-arguments"><span class="token punctuation">(</span>RunFor <span class="token operator">=</span> SerializerType<span class="token punctuation">.</span>NewtonsoftJsonNet<span class="token punctuation">)</span></span></span><span class="token punctuation">]</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">TestWithCustomFact</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token class-name"><span class="token keyword">var</span></span> session <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DocumentSession</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token class-name"><span class="token keyword">var</span></span> doc <span class="token operator">=</span> session<span class="token punctuation">.</span><span class="token generic-method"><span class="token function">Load</span><span class="token generic class-name"><span class="token punctuation">&lt;</span><span class="token keyword">dynamic</span><span class="token punctuation">></span></span></span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">)</span><span class="token punctuation">;</span> Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"1"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">)</span>doc<span class="token punctuation">.</span>Id<span class="token punctuation">)</span><span class="token punctuation">;</span> Assert<span class="token punctuation">.</span><span class="token function">Equal</span><span class="token punctuation">(</span><span class="token string">"test"</span><span class="token punctuation">,</span> <span class="token punctuation">(</span><span class="token keyword">string</span><span class="token punctuation">)</span>doc<span class="token punctuation">.</span>Name<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre></div> <p>You can implement the custom <strong>XUnit Theory</strong>. But, I’ll let you check it directly on GitHub: <a href="https://github.com/oskardudycz/XUnit.MatrixTests/blob/main/XUnit.MatrixTests/Extras/SerializerTypeTargetedTheory.cs">https://github.com/oskardudycz/XUnit.MatrixTests/blob/main/XUnit.MatrixTests/Extras/SerializerTypeTargetedTheory.cs</a>.</p> <p>As the final touch for this blog post, I’ll show you how to configure such a test matrix in GitHub Actions:</p> <div class="gatsby-highlight" data-language="yml"><pre class="language-yml"><code class="language-yml"><span class="token key atrule">name</span><span class="token punctuation">:</span> ASP.NET Core CI <span class="token key atrule">on</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>push<span class="token punctuation">]</span> <span class="token key atrule">jobs</span><span class="token punctuation">:</span> <span class="token key atrule">build</span><span class="token punctuation">:</span> <span class="token key atrule">runs-on</span><span class="token punctuation">:</span> ubuntu<span class="token punctuation">-</span>latest <span class="token key atrule">strategy</span><span class="token punctuation">:</span> <span class="token key atrule">matrix</span><span class="token punctuation">:</span> <span class="token key atrule">serializer</span><span class="token punctuation">:</span> <span class="token punctuation">[</span>Newtonsoft<span class="token punctuation">,</span> SystemTextJson<span class="token punctuation">]</span> <span class="token key atrule">steps</span><span class="token punctuation">:</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Check Out Repo <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/checkout@v1 <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Setup .NET Core <span class="token key atrule">uses</span><span class="token punctuation">:</span> actions/setup<span class="token punctuation">-</span>dotnet@v1 <span class="token key atrule">with</span><span class="token punctuation">:</span> <span class="token key atrule">dotnet-version</span><span class="token punctuation">:</span> <span class="token string">'5.0.100'</span> <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Build with dotnet <span class="token key atrule">run</span><span class="token punctuation">:</span> dotnet build <span class="token punctuation">-</span><span class="token punctuation">-</span>configuration Release <span class="token punctuation">-</span> <span class="token key atrule">name</span><span class="token punctuation">:</span> Test with dotnet <span class="token key atrule">run</span><span class="token punctuation">:</span> dotnet test <span class="token punctuation">-</span><span class="token punctuation">-</span>configuration Release <span class="token key atrule">env</span><span class="token punctuation">:</span> <span class="token key atrule">DEFAULT_SERIALIZER</span><span class="token punctuation">:</span> $<span class="token punctuation">{</span><span class="token punctuation">{</span> matrix.serializer <span class="token punctuation">}</span><span class="token punctuation">}</span> </code></pre></div> <p>Yes, it’s so simple. You need to define the set of matrix variables (<strong>serializer: [Newtonsoft, SystemTextJson]</strong>). And pass them to the build step environment variable.</p> <p><strong>Magic! Smoke and Mirrors!</strong></p> <p>Breeding sheep in Bieszczady is a decent idea. But programming still can be fun. I hope that this post will help you and save you some of the head-banging-on-desk experience.</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[What's the difference between a command and an event?]]>https://event-driven.io/en/whats_the_difference_between_event_and_command/https://event-driven.io/en/whats_the_difference_between_event_and_command/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/77c518b6cddd40c489ead18886a3a877/d2429/2021-02-17-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9ADglG041K4NeTFI9MFA2KTAgGX5SPYtWOndJMcGuof////38+v/+/f788f786v/54PztzPbftfbdsfrjugBCKR11SzlvTT0KCAURBwVPMieWZ09vQi1rPym8pZX/+en36tvv5drbwqvjyK/jx6n56cn658D13rPqyZwANyQabkg2RzEmBgMBXTkronFZiGRQTS0fbkEsvq2f///6+vDi1byo0auO4b2ZqYNixaiG/+3F9N2x4ruFAD4nHFc4KkQuJAQEAzciGXxWRH9hT187KY9YPMq2qf////ThxXVWRK2WgJ6Ic1g4JM6ykv3x1+/Zs+TBiwBJLSFWNypCLSIPCQUvHRaEY0+KZVCZZkvGknPNvKv04snevJ13YE0vIhYqIRh1VT7arITx2bj8+Or2798ADgoICwcFAAAAKg8IzYtlpopuKBoVYUU0xqyV///2/Pv8////qZeGUj0raVA7lnVY793F6cyo6MOZ+/HcAAcGBgAAAAEBAQAAAD4sIBYSDgAAAAAAAA0IBbCkmdHKwfLs4YJzZWpLMqaHZHtjTvTjy/vrzuXEm+i3gwAIBwYAAAAWDgo8JBoSBwUlFA5FKyEdEg4AAABsZV5bSTpSQzdKNyriqnbuxI9rVUOJeGdpUkDIt57/+NsABwcGAAAACwcGGxEMJRoWakY3Y0ExHxcTAAAAUE1JRzoxQzMoOCgehF5ChmZINicdWEMyPSwiyKiE/+OzAAUEAwkJCZOTlImKinJ0dJ+cmq2im7ywqpiCdYV5bk1BN5WKg8C7tpeTkJKNiraup5iMgoJ+emlRQJVtTwAGBgUCAgM2NjUwMDAlIyE1NjU0NDNMQj1eQzXHuKl/cWRpU0iPem55Zlh/bV6NeWt3ZVlkVUhhUEGPd15nwCWqcFdzpwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/77c518b6cddd40c489ead18886a3a877/a331c/2021-02-17-cover.png" srcset="/static/77c518b6cddd40c489ead18886a3a877/36ca5/2021-02-17-cover.png 200w, /static/77c518b6cddd40c489ead18886a3a877/a3397/2021-02-17-cover.png 400w, /static/77c518b6cddd40c489ead18886a3a877/a331c/2021-02-17-cover.png 800w, /static/77c518b6cddd40c489ead18886a3a877/d2429/2021-02-17-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>What’s the difference between a command and an event?</strong></p> <p>The answer seems apparent, but let’s see if it’s straightforward.</p> <p><strong>The command represents the intention.</strong> It targets a specific audience. It can be your friend when you’re asking “pass me the salt”. It can be an application service and request with intention to “add user” or “change the order status to confirmed”. So the sender of the command must know the recipient and expects the request to be executed. Of course, the recipient may refuse to do it by not passing us the salt or throwing an exception during the request handling.</p> <p><strong>The event, in turn, represents a fact in the past.</strong> It carries information about something accomplished. What has been seen, cannot be unseen. Following our examples “user added”, “order status changed to confirmed” are facts in the past. We do not direct events to a specific recipient, we broadcast the information. It’s like telling a story at a party. We hope that someone listens to us, but we may quickly realise that no one is paying attention.</p> <p><strong>What the event and the command have in common?</strong></p> <p><strong>Both are messages.</strong> They convey specific information; a command about the intent to do something, or an event about the fact that happened. From the computer’s point of view, they are no different. Only the business logic and the interpretation of the message can find the difference between event and command.</p> <p><strong>Commands are usually assumed to be synchronous and events to be asynchronous.</strong> We typically send commands via, e.g. Rest API, events via queues (In-memory, RabbitMQ, Kafka etc.). This distinction comes from custom. When we’re sending a command, we’d like to know immediately whether it has been done or not. Usually, we want to do an alternative scenario or error handling when the operation failed. Likewise, we typically assume that it is better to immediately stop the process, e.g. buying a cinema ticket than wait and refresh and see if it has worked.</p> <p>It makes sense, but it’s not always so obvious. For example, a bank transfer: when we make it, it won’t happen right away. We have to wait for a while. It’s the same when making a purchase on the Internet. Placing an order and making a payment doesn’t immediately finish the whole process. It still has to be shipped, the invoice issued, etc. This is an asynchronous process, so the results of our commands may also be asynchronous.</p> <p>Microservices and distributed systems add additional complexity.</p> <p><strong>Traditionally, the sender needed to know who to tell to take over the rest of the work. Using queuing/streaming systems reverse the services’ dependency.</strong> The sender publishes the message to the unified channel, the recipient subscribes and waits for it. When we buy a cinema ticket, a receipt must be generated, the seat must be reserved, the user should be notified by e-mail and displayed on the website. All that can be split into separate workflow steps. Which one should be triggered by a command and which one by the event?</p> <p><strong>We could use heuristics: we send the command when the recipient has the right to refuse the request and event when the recipient just accepts it.</strong> If we want to add a user, the system may refuse when we used the existing username. Can a financial service refuse the reservation service to generate a receipt? If the reservation is confirmed, transfer was made, then the financial module should just accept it. So, is it an event?</p> <p>In theory, yes, if there are no validation rules and if the event happened then, the other system should accept it and perform the logic. Kafka-like streaming systems should guarantee delivery even if the financial service is temporarily unavailable. We have bugs in the code? Then we have to fix them.</p> <p>Here, the theory ends, and practice begins. Our customer doesn’t care if the error is a bug that we know and fix it in the near future? The customer wants to do a business and be operational.</p> <p>Of course, in the message queuing tutorials, you can find suggestions like: <em>“There is a dead-letter queue/poison-message queue where not-processed messages will be put. You can check there and react”</em>. That’s cool, but how many people are monitoring it? And even if they do, how quickly will they be able to react? Let’s assume that we collect metrics, send alerts, support responses quickly, and report a ‘ticket’ to programmers. How quickly will they fix it? We can ease that with the design upfront and adding the compensating operation (e.g. button in the UI to generate the invoice if the reservation confirmed event was lost).</p> <p>Sometimes it turns out that a given event will always have only one recipient. What’s more, we expect it to be always handled. Is it still an event? It is worth considering whether we’re not sending the command “issue a receipt” under the event with the “reservation was confirmed” name. When the business process is crucial, we may prefer to fail fast and have an immediate result. For such cases, it’s worth considering whether it would be better to do an asynchronous command instead of an asynchronous event.</p> <p>Of course, we’re touching here on the separate topic of the distributed processes. By itself it’s non-trivial. You can read more on that in my other posts:</p> <ul> <li><a href="https://event-driven.io/en/saga_process_manager_distributed_transactions/">“Saga and Process Manager - distributed processes in practice”</a></li> <li><a href="https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">“Outbox, Inbox patterns and delivery guarantees explained”</a></li> </ul> <p><strong>The distinction between commands and events may not be so simple as it looks. We need to take many factors into consideration:</strong></p> <ul> <li>Is it an intention to do something or the recorded fact?</li> <li>Is it asynchronous or synchronous operation?</li> <li>Can it have multiple recipients?</li> <li>Is it business-critical?</li> <li>And many more</li> </ul> <p>If someone asks you “is it a command or an event”, don’t be afraid to say “it depends” and start to work on the business logic understanding.</p> <p>Oskar</p><![CDATA[Saga and Process Manager - distributed processes in practice]]>https://event-driven.io/en/saga_process_manager_distributed_transactions/https://event-driven.io/en/saga_process_manager_distributed_transactions/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/07398c056d2d8a5528ebf73b635e9088/d2429/2021-02-10-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9ADAPHBoMFwQJEwAKEgEJEAoWHoZbWeOSf7t1ZatcVFQoLMKjj9G4p729srm1qXp0aFhZUz5AQwAACQUHEgBzCyN7DSZ7DyhpDiZBBxk8Nj2GZGTAfXDXdWPWXFZYJy2TemnKln2kh3eIY1iDWUuBb2DBlYiDXVxaREcAUjMvQSEjLggWXRMkXxUmRCYqUUJBVj88rldRwUZJdRkpo2hh0JuEwIRpsHRfllhKczs0yIF2zIB21I6DAKZ1WFI3Lx8bIhMUHCUhJpVsYMeVgpFpW2AiKb08RXI5PmBAQJ5tX7h6ZZ5bTYtPQ611abtvaLFmX8uIfQBuSD6NXUmia12lcmVKMzSfcWvLlofSo46WXVqlMDssLjUAAAtBJSiJVlF2Qz6JYlSxd22zYFupYFnQjIAAdUk+cEM5qmZY4I15bjo5poB01pyO0Z+EpmFbiBkqIxsjTjU2iD8/smlimFZRpIh5h09Mj0dGkExJwXdqAEcyMG1HP7hpXJJKRlEVIJdlW8GPeotZS61WS8RsZsp8bcJiWIEqL6pYU9F/csKelq58dEsZIIBFQdSukAB3TEisfG+SQkJHHyZlQT/PnYnGjnSaVD7Rj23Vl4CzY1igTEmGOz2hSUnJenC4enOzi4JDGB6neWTBkncAaDE0m11XfD87rYBo4sGm58CpvIJnqGpU4KuM6Mau1KaQqXVkX0tMj1BPumBcr2Nhj2Nhajs7mmBRuIZyAEMdI3A+OM+be/DNs+O1lOXCrM2Vetahh+zGsOe5m+/Pt+64kUYzLykiK5tQULJcWpVQUI5TUYZAQHA/PwBjJzCQUkHTmGzNmn3QlnvhvKngsZfanX7itJvou53OhmzPhGOQTT4wChdaJi+BLDakXVioXVeSTEpGGiJzHRsI5Hi2ngAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/07398c056d2d8a5528ebf73b635e9088/a331c/2021-02-10-cover.png" srcset="/static/07398c056d2d8a5528ebf73b635e9088/36ca5/2021-02-10-cover.png 200w, /static/07398c056d2d8a5528ebf73b635e9088/a3397/2021-02-10-cover.png 400w, /static/07398c056d2d8a5528ebf73b635e9088/a331c/2021-02-10-cover.png 800w, /static/07398c056d2d8a5528ebf73b635e9088/d2429/2021-02-10-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>What can go wrong with distributed systems?</strong></p> <p><strong>Everything!</strong></p> <p>I like to compare distributed systems to Rocky Balboa fighting the last round with Apollo Creed. He is staggering on his feet. His face is bruised, left hand is hanging, his eyes are black. Rocky is struggling but still pushing forward. And that’s good! On the other hand, the monoliths are standing firmly until the fight starts. However, when Monolith get a shot, it usually ends up with the KO.</p> <p>There were times when I thought that I can have control over the distributed environment. I tried to configure distributed transactions using WCF and MSDTC (if you don’t know any of these acronyms then be happy and don’t even google them). I succeeded in a way that it was sometimes working. Usually, it was not.</p> <p><strong>Why are distributed transactions so difficult or even impossible to achieve?</strong></p> <p>It’s hard because at any given point in time:</p> <ul> <li>all services must be available and responsive,</li> <li>the coordinator that manages the transaction must always be available to process individual operations,</li> <li>it must be fast enough to stop a large queue forming.</li> </ul> <p>If we also add:</p> <ul> <li>deadlocks can be problems even in regular transactions,</li> <li>maintaining the appropriate transaction isolation levels is difficult even for one service,</li> <li>in case of an error in one service, we have to roll back changes made on other participants.</li> </ul> <p>All this makes distributed transactions <a href="https://en.wikipedia.org/wiki/Peter_Buckley_(boxer)">Peter Buckley</a> type of fighter. They should not go into a real production battle.</p> <p><strong>So what are the alternatives?</strong></p> <ul> <li>Saga,</li> <li>Process Manager,</li> <li>Choreography.</li> </ul> <p>All three basically boil down to one principle: do it yourself. We do not use any particular technology to manage transactions. So how does it work?</p> <p><strong>The first step is to write out the process.</strong> It can look like that:</p> <ul> <li>The customer initiates the shopping cart.</li> <li>Adds products to it.</li> <li>Confirms the purchase.</li> <li>At this point, the order process begins.</li> <li>The first step is to pay for the selected products.</li> <li>If it is successful, the order changes its status to paid, and we start the shipment process.</li> <li>If the shipment is successful, we mark the order as completed.</li> </ul> <p><strong>What if one of the operations fails?</strong></p> <ul> <li>If the payment fails, we will cancel the order.</li> <li>When the shipment fails (e.g. because the product is no longer available), we cancel the credit card charge if it’s possible. If not then we’re sending a request to refund.</li> </ul> <p>Of course, the described process is simplistic. It’s like that to focus on the concept rather than the specific business case.</p> <p>The next step is to determine which module/service should handle each step of the process. We can divide it, for example, between modules:</p> <ul> <li>shopping cart management,</li> <li>orders,</li> <li>payments,</li> <li>shipment.</li> </ul> <p>Well, now we really need to:</p> <ul> <li>Define the events that occur in the process, e.g. the basket is confirmed, products paid, products shipped.</li> <li>Define requests (commands) that cause them, e.g. start an order, pay for products, send products).</li> </ul> <p><strong>Having commands and corresponding events, we can build a “sandwich” - action => reaction, command => event.</strong></p> <p>As we have the process written out and divided into modules, we can determine where we can run local transactions. Usually, within one module, primarily if we use relational databases, we can have transactions. Why? Because we typically have one database.</p> <p>That is why, in fact, instead of making one large distributed transaction, we create a flow consisting of many small transactions. Each module should take care of the correct handling and notify others that the operation was successful or not.</p> <p>How does it do this? Preferably through an event. So if we managed to send the package, we trigger an event. If there is no product in the warehouse, we send an event that the parcel could not be sent. If we send events over a durable message bus (such as Kafka or RabbitMQ), we get an even greater fault-tolerance. If a given service is temporarily unavailable, the process will not fail. When the service comes back to life, it will handle the event.</p> <p>Of course, you can send commands synchronously. You can use in-memory buses. It depends on us how strong the reliability guarantees we want to provide. I’d recommend finding those constraints from the business and then choosing the tools based on them.</p> <p>An important thing to note is that <strong>we have to avoid the workflow freezing during the processing</strong>. How do we achieve that?</p> <ul> <li>Wrap the entire command handler in try/catch, and send back a failure event.</li> <li>This will not protect us by 100%. It is always worth having a background worker that will send an event when the maximum processing time was reached. Thus we can cancel it.</li> <li>We should think of the compensation procedure triggered manually. It’ll be an ace up our sleeve when other methods fail.</li> </ul> <p>Let’s get back to Sagas etc. <strong>We can divide the coordination into two main approaches</strong>:</p> <ul> <li><strong>Orchestration</strong>, where one service works as a conductor and drives it all via waving of a baton,</li> <li><strong>Choreography</strong>, when the services react to each other’s operations.</li> </ul> <p>The advantage of choreography is that we do not have the so-called “single point of failure”. We have an orchestra that may continue to play without a conductor. The downside is that we have a fragmented process that is difficult to grasp. From the perspective of a programmer of one module developer, we know that when we get such a command, we have to send such an event, etc.</p> <p>It’s excellent for the more straightforward scenarios, as it cuts complexity. For more complex cases, it is crucial to know the big picture of the process. Without it, it’s hard to manage it. We need to remember that business is not interested in our internal technical split. They are interested in the end result, i.e. the smooth operation of the workflow.</p> <p>Orchestration brings coupling, but sometimes it’s good as it shapes the precise boundaries of responsibility. That helps with coordination.</p> <p><strong>Saga and Process Manager are examples of coordination.</strong></p> <p>The term saga comes from the <em>“SAGAS”</em> whitepaper written in 1987 by Hector Garcia-Molina and Kenneth Salem (<a href="https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf">https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf</a>). It describes it as the solution for the Long Lived Transactions (LLT).</p> <p><em>“A LLT is a saga if it can be written as a sequence of transactions that can be interleaved with other transactions. The database management system guarantees that either all the transactions in a saga are successfully completed or compensating transactions are run to amend a partial execution”</em></p> <p><strong>A saga</strong> doesn’t have to know where the event comes from and where the command is going. It <strong>is basically a “stupid” coordinator/dispatcher</strong> who:</p> <ul> <li>Waits for the event.</li> <li>When a success event arrives, dispatches a command based on the event data (and only this data).</li> <li>When a failure event arrives, send the command to start compensation (e.g. refund the payment). It’s important to know that a saga should support the “rollback” of all the actions that happened before the failure event was recorded.</li> </ul> <p>Thanks to this, a saga is lightweight. It can be put into the module together with other logic. It can also be pulled out to a separate one.</p> <p><strong>This is what makes Saga different from Process Manager.</strong> The Saga itself has no state. A Process Manager can be modelled as a state machine. It makes decisions based not only on incoming events but also the current state of the process.</p> <p>For me, this distinction is essential. The “stupid” Saga is much more flexible and gives less overhead than a Process Manager. There are just fewer places where something can go wrong. So unless I have a really complex workflow, I’d try to avoid using a Process Manager.</p> <p>How to use Saga when data from the event is not enough? We’re getting into a dangerous area, but we must go on.</p> <p>We can get a little help from an aggregate (e.g. Order, for the process described above). For example, after the payment is completed, we should send the ordered product. Payment Completed event does not contain any information about a specific product: it only carries information about how much you have to pay. Though, we have this data in the order aggregate. As we need to mark the order state as paid, then we can send such a command. The saga listens for an event confirming the order status change. Such an event will have the needed information about order and the saga can proceed with sending the shipment command. Of course, this is a bit of a “trick”. Someone might say it’s an implicit Process Manager, but in my opinion, it is a simple, pragmatic rule showing how you can create stateless sagas.</p> <p>Sample saga in pseudocode looks like:</p> <div class="gatsby-highlight" data-language="csharp"><pre class="language-csharp"><code class="language-csharp"><span class="token keyword">class</span> <span class="token class-name">OrderSaga</span> <span class="token punctuation">{</span> <span class="token comment">// Happy path</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>CartFinalized <span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">SendCommand</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">InitOrder</span><span class="token punctuation">(</span> <span class="token keyword">event</span><span class="token punctuation">.</span>CartId<span class="token punctuation">,</span> <span class="token keyword">event</span><span class="token punctuation">.</span>ClientId<span class="token punctuation">,</span> <span class="token keyword">event</span><span class="token punctuation">.</span>ProductItems<span class="token punctuation">,</span> <span class="token keyword">event</span><span class="token punctuation">.</span>TotalPrice <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>OrderInitialized <span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">SendCommand</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RequestPayment</span><span class="token punctuation">(</span> <span class="token keyword">event</span><span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> <span class="token keyword">event</span><span class="token punctuation">.</span>TotalPrice <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">public</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>PaymentFinalized <span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">SendCommand</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">RecordOrderPayment</span><span class="token punctuation">(</span> <span class="token keyword">event</span><span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> <span class="token keyword">event</span><span class="token punctuation">.</span>PaymentId<span class="token punctuation">,</span> <span class="token keyword">event</span><span class="token punctuation">.</span>FinalizedAt <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>OrderPaymentRecorded <span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">SendCommand</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">SendPackage</span><span class="token punctuation">(</span> <span class="token keyword">event</span><span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> <span class="token keyword">event</span><span class="token punctuation">.</span>ProductItems <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>PackageWasSent <span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">SendCommand</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CompleteOrder</span><span class="token punctuation">(</span> <span class="token keyword">event</span><span class="token punctuation">.</span>OrderId <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Compensation</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>ProductWasOutOfStock <span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">SendCommand</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">CancelOrder</span><span class="token punctuation">(</span> <span class="token keyword">event</span><span class="token punctuation">.</span>OrderId<span class="token punctuation">,</span> OrderCancellationReason<span class="token punctuation">.</span>ProductWasOutOfStock <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token return-type class-name"><span class="token keyword">void</span></span> <span class="token function">Handle</span><span class="token punctuation">(</span>OrderCancelled <span class="token keyword">event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">SendCommand</span><span class="token punctuation">(</span> <span class="token keyword">new</span> <span class="token constructor-invocation class-name">DiscardPayment</span><span class="token punctuation">(</span> <span class="token keyword">event</span><span class="token punctuation">.</span>PaymentId<span class="token punctuation">.</span>Value <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre></div> <p>I recommend to also read my <a href="https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">post about the delivery guarantees</a> to know how to make sure that all events and commands will be delivered.</p> <p>You can also check the real implementation written in C#:</p> <ul> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/ECommerce/Orders/Orders/Orders/OrderSaga.cs">Order Saga</a></li> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Sample/ECommerce">Whole solution with services split, external buses and integration with CRUD approach</a></li> </ul> <p>I explained also how to model distributed processes in detail in the <a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed">webinar about implementing distributed processes</a>:</p> <p><a href="https://www.architecture-weekly.com/p/webinar-3-implementing-distributed"><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAYAAAB/Ca1DAAAACXBIWXMAAAsTAAALEwEAmpwYAAAB8klEQVQoz4WST2sTURTF5wO59du49QO4kSBVQdwJbsRWwbiK1Z24UBQL8R8aCCQpaWtCkjYTJmmczLy/M4kxTX/yJk1sCsXF4XJ595173jnPG40ipFTEsVggEkgpkXJZJSIaoZRCa4PWOqtKacIwwhhLZfeAexv3+fD+G54xBgfrYC3GLvqLWBA5qFVVSjI/nbNdKPB08zG53AbecqtTIOIYLRXqrM/gLkuJ0RprE6xNscZikglCWWazGc/yeX62aty5ewsvikQ2HOmE6n6N+OAd0phse0bonj2eUu60qVWKNHc/U23u0f3+ClHf4c8cnufzFN/skLtxE8/5tpCvEUIgYx+pdearI7XOgu4hYbfDcdhnOOgy+DVgFDSJB0ecnM7ZerTF5tWXXLtyHc8RLTxSmCQl6vsMK2+JghZKGWpBiH1wm5PCE/RkitFnXidjtElIkoR2q037q0+5WMFzqWaESmVDUa/B8McLouYntJJ88UP62w8ZV0vIJM3m1sPRpGnKlN+EcfgvlJXCXpvj0mtCp7JTp+qH+JWPaHs+6XUoqbKg/K6P5+QvD4xNEIOAqFFCHJaRfp39fsxwr4QRMdpcTuqe3uv1nML1A+P8SSfZt9DJmNSFkk7OferLCYMgwHPJmoubM58WUKte/5ew0WjyFxc6K8nB1bJ+AAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2023-09-22-webinar.png" srcset="/static/aeaac4b117eefd05e7a7793b42fef48d/36ca5/2023-09-22-webinar.png 200w, /static/aeaac4b117eefd05e7a7793b42fef48d/a3397/2023-09-22-webinar.png 400w, /static/aeaac4b117eefd05e7a7793b42fef48d/a331c/2023-09-22-webinar.png 800w, /static/aeaac4b117eefd05e7a7793b42fef48d/e4699/2023-09-22-webinar.png 957w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </span></a></p> <p>Additionally, I have prepared a set of recommended materials about distributed processes, <a href="https://github.com/oskardudycz/EventSourcing.NetCore#105-distributed-processes">read more</a>.</p> <p>Read also more in:</p> <ul> <li><a href="/en/event_driven_distributed_processes_by_example/">Event-driven distributed processes by example</a>.</li> <li><a href="/en/how_to_update_past_data_in_event_sourcing/">Oops I did it again, or how to update past data in Event Sourcing</a>,</li> <li><a href="/en/set_up_opentelemetry_wtih_event_sourcing_and_marten/">Set up OpenTelemetry with Event Sourcing and Marten</a>,</li> <li><a href="https://github.com/oskardudycz/EventSourcing.NetCore#105-distributed-processes">Set of recommended materials about distributed processes</a>.</li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[What if I told you that Relational Databases are in fact Event Stores?]]>https://event-driven.io/en/relational_databases_are_event_stores/https://event-driven.io/en/relational_databases_are_event_stores/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/af61881812c7a3f663e9b569f41a8d6b/d2429/2021-02-03-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACG0lEQVQoz2NgYGBgZGRkYGBg4WRp6Kzdu3fbru1rVi6eNqWvYff2tQf2bDx+YNOJo3vnzexat3pxRVkuJycLWAsDDIBZvAK8NoHWc5dOfv3geH9Tcn1xzOTWvN6GrAmt+Q9vHVu7oKK1LCA2yFSAjx27ZgtfwwUrJz29eUBVRZKDk1VXTZyfh52TgyUpIeD8vkmT6n2rc1yUZIXgjkVo5uTl8gy1Pry6feHkUk5uNk4ejo6qxIIUfxERHh83szMbO+Z1hzQWe6orizMwMDDBNTNCbeb2DLFa3VewdEq5shSvurzYxMqoWfXxYS46eRE2W+cWz5uWWJrtrKYshqYZxBIR5bMNMJs2tfLsznneZvKeNppSItxuVprTG2ILIqw7G2Iza0OLG4I11CVBmplQNfOL8NonOsXWxJ49srw+x9vZRkNPVcrFQrs0xaOryDcp3klcU9rK21BWQRTNzyAWjzCfU66fdYbH0jU9m2eVGmuKK4jzmGnJxProh7qpO9rpREZ5cvKxCQnxoIU2iMklxGOX6V3cXlpZk7tlxeTWhqL4mKCk2ICGyoyK4rT09ISZ0zuU1KR5uFGjihGmObMhd/bMyR1tTX19XWvWLJs8sWfalP5pUyfMmT191crFq1YsTEyK4OPnRnE2hCUoLDhv6awpM3vS0xLLywozM5K9vdwaGqrTUhMCArzLy4pa2hrWrJlnZ2MKDjAmoE4Ahx+tMrRXdnAAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/af61881812c7a3f663e9b569f41a8d6b/a331c/2021-02-03-cover.png" srcset="/static/af61881812c7a3f663e9b569f41a8d6b/36ca5/2021-02-03-cover.png 200w, /static/af61881812c7a3f663e9b569f41a8d6b/a3397/2021-02-03-cover.png 400w, /static/af61881812c7a3f663e9b569f41a8d6b/a331c/2021-02-03-cover.png 800w, /static/af61881812c7a3f663e9b569f41a8d6b/d2429/2021-02-03-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Are you one of those people saying that Event Sourcing is detached from reality?</strong> Do you think that you don’t need such extravagance, as you have relational databases? Why go wild? What if I told you that relational databases use the Event Sourcing approach? First, let’s recall how Event Sourcing works. The source of truth lies in the event log. Every business operation results in a new set of events. When they’re appended, we can rebuild our read models (projections, snapshots, etc.) on their basis. What’s more, as our source of truth is the events, we might remove these read models and rebuild them based on events.</p> <p><strong>Relational databases (and databases in general) have a concept called “Write-Ahead Log” (WAL) or “Transaction Log”.</strong> What is this? It is a data structure that stores all operations taking place in database transactions (e.g. add a record to orders table, update a shipment record, delete a user record). When we commit a transaction, the data are stored at first in the Write-Ahead Log, then they are applied to tables, indexes, etc. Hence the name “Write-Ahead”: from this writing data to the log in advance of other changes. Why does it work that way?</p> <p><a href="https://www.youtube.com/watch?v=WIUAC03YMlA">Let’s go deeper underground</a>. The data in the database is nothing but physical files stored on disk. They vary in format depending on the database type, but they have to be physically saved somewhere in the end. Even if we use a Docker, Kubernetes or Cloud, there is a physical disk somewhere that keeps the data. It may take some time to update tables and indexes based on the transaction changes. In the worst case, it might take even hours. During this time, the operating system may restart, a disk write may fail, or some other random error can occur.</p> <p><strong>This is where “Write-Ahead Log” helps.</strong> It is a structure optimized for appending. Each subsequent operation is added one after another at the end of the log. Because it is optimized for addition, it can accept and save operations much faster than performing them directly on tables. If we successfully added a new entry to WAL, we are safe even if applying to tables failed. Even restarting the server won’t be disastrous. After the restart, the database will continue from the last processed log entry and complete the transaction (or roll it back safely).</p> <p><strong>Therefore, in databases, the truth lies in the “Write-Ahead Log”.</strong> On this basis, read models‐tables are rebuilt (that is, records are added, updated, deleted). What’s more, the operation itself also happens asynchronously (with eventual consistency maintaining the order of operations). There is a delay between appending to the WAL and the appearance of changes in the table.</p> <p><strong>So precisely like in Event Sourcing.</strong></p> <p>Well almost, a WAL is rather “Command Sourcing”. We do not record the facts (events), but the intention to do something. WAL is also a temporal structure: it’s purged after some time if the changes were applied. In the Event Store data is usually kept much longer. The rest is correct.</p> <p>Are you surprised?</p> <p>Oskar</p> <p>p.s. If you want to read more about it, check those links:</p> <ul> <li><a href="https://www.postgresql.org/docs/13/wal-intro.html">https://www.postgresql.org/docs/13/wal-intro.html</a></li> <li><a href="https://developers.eventstore.com/server/v20/server/indexes/#overview">https://developers.eventstore.com/server/v20/server/indexes/#overview</a></li> <li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/logs/the-transaction-log-sql-server?view=sql-server-ver15">https://docs.microsoft.com/en-us/sql/relational-databases/logs/the-transaction-log-sql-server?view=sql-server-ver15</a></li> <li><a href="https://www.sqlshack.com/sql-server-transaction-log-part-1-log-structure-write-ahead-logging-wal-algorithm/">https://www.sqlshack.com/sql-server-transaction-log-part-1-log-structure-write-ahead-logging-wal-algorithm/</a></li> <li><a href="https://www.sqlite.org/wal.html">https://www.sqlite.org/wal.html</a></li> <li><a href="https://medium.com/@daniel.chia/writing-a-database-part-2-write-ahead-log-2463f5cec67a">https://medium.com/@daniel.chia/writing-a-database-part-2-write-ahead-log-2463f5cec67a</a></li> </ul> <p>p.s.2. Read also <a href="/en/lets_build_event_store_in_one_hour/">how event stores are built</a> and why it may be worth <a href="/en/event_stores_are_key_value_stores">thinking about them as key-value stores</a>.</p><![CDATA[What texting your Ex has to do with Event-Driven Design?]]>https://event-driven.io/en/what_texting_ex_has_to_do_with_event_driven_design/https://event-driven.io/en/what_texting_ex_has_to_do_with_event_driven_design/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/e25d56648ee9295043c309919e9bc62b/d2429/2021-01-27-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AHBubnJwcHFvb3FwcGZhYjUiI0knInRDP4ZQS3ZWU15bXGllZ2NeYXNwdHt7fX5+gH9/gYKCgoWFhYSFhgBwbm5vbW1ta2txcHBGOjwtDgx1SECfYVq4cmuzb2plRkZFP0FKQ0NrZ2xycnV7ent/fn+AgYCEg4SFhYcAbGtra2pqaWhoa2trKiEkKxEQa0Q+glRQrXJuqnFufFdWOjIzJiEeWlldb3F1aVxaa1xbgYKHgoKCiImHAGhnaGloZ2ZlZGtoZyYiIyANC2VCPl9BQotWUo1gX25QUD82OSIdHC4rKlNRVGdjY1pIUmBcUX15dmVaagBNTU5DTVBMVFdWXF5LODkzGBZ9UEyGUlB+S0e4eHWVY2FaUlRnZWZFQEQ+PEFeWV1NRUtCPDNXT0xXSFgAOTY4DRghFycxFy44RDIzWTApc0xJjFdXi1ZbuHp5r3RvZVZcY2NoS0pNMS8tRjk+PjE2SD1QRzk9YFdQACEhIiInKT9GTj9JVD5FTk03NmxJRINVVJhaYLd3dJdoYSohIykmKRYUFQ0JC0lAKUpBMVZMWF5PRklCRwAiJio3ODhFSU5NV2JPXmxMSVBkSER3VVSZaGerenV4WlYhHxk5My4cFxkZFxk4Ni9QTkFuZWR9c25mY2AAfHd2w7+/l5eYlZaay87Qtba5ube2lYmIt6iovra1wr+/uLa2l5SUmZeZvb6/np2eqaWkx8bFzczMpqShAGdHPHpgV4h5daacm8rDwaKTj6CIhZx9eqmKhraTj8Cuq8jCwrWtrL62tMS+vLewr5KJhqagm7Cop35/fwAzCQJmRDyvnZnGt7W9rqyxnpygfnmofHanfXe6lpDGs7HBtLK9rq3As7LCtLLFuLauoqCUg3+XiYdZU1QZ/hDg936ZbgAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/e25d56648ee9295043c309919e9bc62b/a331c/2021-01-27-cover.png" srcset="/static/e25d56648ee9295043c309919e9bc62b/36ca5/2021-01-27-cover.png 200w, /static/e25d56648ee9295043c309919e9bc62b/a3397/2021-01-27-cover.png 400w, /static/e25d56648ee9295043c309919e9bc62b/a331c/2021-01-27-cover.png 800w, /static/e25d56648ee9295043c309919e9bc62b/d2429/2021-01-27-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>We sometimes feel melancholic, blue and a bit messy. When we enhance those feelings with “gummy berry juice” then various dubious ideas come to our minds. We grab the phone, we bash out a few brilliant sentences. Bam! Here we go! Usually right after we hit “send” we realise that it was a bad idea. Sometimes we have to wait until morning to understand embarrassment:</p> <ul> <li>“Did I really text my ex?!”</li> <li>“Maybe she didn’t read it?”</li> <li>“Maybe I can remove it or at least edit it?“,</li> </ul> <p>Sometimes we are lucky, e.g. we can delete a message on WhatsApp, or edit it on Skype. However, we aren’t sure if the message was read or not. Usually, the best thing to do is apologize or write “I was just kidding!“. You cannot retract a bad situation, but you can try to fix it.</p> <p>Why am I writing this to you? People often ask me, what will happen in event-driven systems when we publish a wrong event (e.g., missing data, incorrect data, etc.). The recipe is similar to the scenario when texting your ex:</p> <ol> <li><strong>Think upfront:</strong> This can save you a lot of trouble and difficulties. Make sure that you correctly understood the logic with the Business, then write tests. Such an approach can protect you from bad consequences.</li> <li><strong>If you’ve sent the message through Skype, edit it:</strong> let’s make it clear, sending a message to an ex after a “gummy berry juice” is never a good idea, even if it seemed like it at the time. However, if the message has not been read yet, there is an escape route: editing it. If we have an event-driven system and send events with a delay (e.g. with the “Outbox Pattern” pattern, read more <a href="https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/">in my previous post</a>), we can still edit it. If we use, e.g. relational database as storage (e.g. using <a href="https://martendb.io/">Marten</a>), we can do it with a migration. Is this “best practice”? Not really, but it works as long as the event wasn’t published yet. Besides, we are not talking here about best practices, but about saving the situation.</li> <li><strong>If we sent a WhatsApp message, try to delete it:</strong> most message buses (e.g. Kafka) allow you to delete messages sent to them. To put it mildly, this is not a recommended solution and has far-reaching consequences. But it can be done. It might not be sufficient, because the message could already be read and deletion won’t help, but it won’t be reread. This is certainly not what I would suggest, but Huston, we have a problem to solve.</li> <li><strong>Apologise:</strong> you can say sorry, you can try to say that someone else got to your phone and did that (“if you were caught red-handed, claim that it is not your hand”). Generally perform the so-called “compensating operation”. It’s just like an invoice. After we’ve issued it, we can’t edit it. We can only correct it. If we transfer too little money to someone, we do not edit the transfer, but we send the second one with the missing amount. Contrary to what “business” used to say, there is always an emergency exit in the process. In an ordinary world, you can always call someone, possibly write an email, to verify they accept your apology. We should do the same in our systems, and we can usually perform a corrective action. This is the only reliable way of making sure that our actions were compensated.</li> </ol> <p>And you know, the basic rule - if you drink, you don’t write!</p><![CDATA[Sociological aspects of Microservices]]>https://event-driven.io/en/sociological_aspects_of_microservices/https://event-driven.io/en/sociological_aspects_of_microservices/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/2443d84c92f4d1beab0b25daea162439/d2429/2021-01-20-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABvElEQVQoz32Sy0sbURTG8/9027WLLqUrd1IRSlWKUu3CtkKREloIpAthFhJ8IFS0Ni0TEOxC4yMSmmZ8ECdE0050hLQ6k4eZZDKve+fcUy4qjBD7LS6c7/LjO+feE8JbMcR9nSTL7l6FXJmOrmnIPOoDMrTcttbUAZAxhgGFgnCuRnK626Zo12vZlVVEJBQIZYSQlPjZdmy8q1CwcAHBdoFh9VSJvZvNqYZmUkS8lAsL3YO6fMwzADrDgGi229nv8aNCYWjpx9hmfThxEk8czM3sfuqd0LIShwOdB2HuUkt3i1Nq/Ot8f/SndPbrvPJ2cm3gmdDV/VpWr3jAfTBl6LstKH2QXoylHz7PCImX8cOns5IQfv+kpz+rNDkMnWEkPrNdbyISW15c30lIC9/yj8JrD8bFwb5Xb0YiqaM//4MR0TCt6Px6MqNUDfJXM6kPxd+Vx+Pi6NSG2bQdCp1nvnar9db04tbH6e0VMX9eNvSapahG5MuhuFtERMejcA/Mz0bT2kqXUpkzvcpj5ZPKxo4aFpKr28eIXqHmtAi7edu7S8Idw2kQ8K6vic+/lBBfLV006i3FgM2yZwbgf4uwTl9YDyeBAAAAAElFTkSuQmCC'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/2443d84c92f4d1beab0b25daea162439/a331c/2021-01-20-cover.png" srcset="/static/2443d84c92f4d1beab0b25daea162439/36ca5/2021-01-20-cover.png 200w, /static/2443d84c92f4d1beab0b25daea162439/a3397/2021-01-20-cover.png 400w, /static/2443d84c92f4d1beab0b25daea162439/a331c/2021-01-20-cover.png 800w, /static/2443d84c92f4d1beab0b25daea162439/d2429/2021-01-20-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Last week, I started writing about the topic of cutting systems into microservices (read more <a href="https://event-driven.io/en/how_to_cut_microservices/">here</a>). I was sceptical about the frequent technical motivations associated with them. I have listed elements that, in my opinion, justify dividing our system into smaller pieces. I have distinguished these as:</p> <ul> <li>the difference in traffic in particular parts of the application (e.g. the administration module does not need as many resources as the search module),</li> <li>the ability to dynamically increase the system capacity with horizontal scaling (e.g. by adding more instances of the sales service on the Black Friday),</li> <li>multi tenancy, i.e. when we do not want the operations made by one client to affect operations of another client,</li> <li>multi-region: when we have a global system, and we want it to work equally fast, e.g. in the USA as in Europe.</li> </ul> <p>Today I would like to put the cat among the pigeons again and talk about the sociological sphere of using microservices. Quite often, we make our decisions not only based on technical or business requirements. The same thing happens for cutting our systems into microservices. Let’s check what the most common reasonings are:</p> <ol> <li> <p><strong>CV Driven Development, Hype Oriented Programming, Hipster As A Service.</strong> These are patterns or doctrines that tell us to use the latest technologies, so we take the most fashionable, trendy, jazzy tools that we can get, usually blindly. I have participated in projects where I joked that we only use libraries with no more than two contributors because we want to discover young talents. I also had a visionary client who did not want to use popular and proven technologies. Although he tried to do an ordinary ETL process, he was always searching for something new instead of using, e.g. MSSQL Integration Services or Reporting Services. He wanted to be an “explorer” and be one of the latest blockbuster solution’s first adopters. I ended up rewriting the solution 3-4 times from scratch using different, weird tools. Quite often we choose technology to have “fun”. There is nothing wrong with that, as long as it coincides with the client’s “fun”. Unfortunately, too often, these two ways are mutually exclusive. That can be a recipe for disaster. We forget that our job is to create systems that just work. They’re supposed to bring in money for our customer. If our decisions make our work enjoyable, then that’s all the better for us. But “first things first”, the most important should be customer satisfaction, not our pleasure.</p> </li> <li> <p><strong>The recruitment aspect.</strong> In our industry, everyone wants to do the latest things, using the latest technologies and the most popular frameworks. It’s the same with microservices. I was involved a few times in conversations with others saying: “Well, I think that we do not need microservices, but without using them, I will not persuade people to work in my company”. Cobol was cool once, now finding a living programmer to code in this might be challenging. But that’s a different story. In my opinion, we should consider whether we, as programmers, are not exaggerating the lust for newer technologies. I am not talking about minimalism and looking for stagnation, but a pragmatic and common-sense approach that we often lack. Do we have to try everything? Maybe we should focus more on delivering business value than trying to use all technologies on earth?</p> </li> <li> <p><strong>Independence and leadership.</strong> When we discuss the possibility of cutting the system into independent modules we’re thinking “this is my moment!“. Now we can finally have our own modules. Now we can separate ourselves from the people we don’t like. We can focus on our work and tell the others “screw you!“. Of course, autonomy is a significant value. We should aim to have self-organising teams. However, with great power comes great responsibility. We have to remember about Conway’s Law:</p> <p><em><strong>“Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.”</strong></em></p> <p>The mere fact that we have independent microservices will not make them the right solution. If the teams cannot get along, so will the microservices they have created. I coined the pattern that I called the “Negotiator Pattern.”; we have two systems that can’t get along, so now we have to add a third one to coordinate them. After that, usually, you end up having three systems that don’t talk to each other. The same applies to development teams. You can read more about that in my <a href="https://event-driven.io/en/architect_manifesto/">Architect Manifesto</a>.</p> </li> </ol> <p>We also have to remember about “Cognitive Load”. It relates to the amount of information that you can process at one time. In other words, how complex is what you’re trying to learn. The higher it is, the more it costs and increases the risk of project failure. See more in the excellent presentation by Manuel Pais and Matthew Skeleton:</p> <p><div class="gatsby-resp-iframe-wrapper" style="padding-bottom: 71.42857142857143%; position: relative; height: 0; overflow: hidden; margin-bottom: 2em" > <div class="embedVideo-container"> <iframe title="" src="https://www.youtube.com/embed/haejb5rzKsM?rel=0" class="embedVideo-iframe" style="border:0; position: absolute; top: 0; left: 0; width: 100%; height: 100%; " loading="eager" allowfullscreen="" sandbox="allow-same-origin allow-scripts allow-popups"></iframe> </div> </div></p> <p>To sum up, we’re usually focusing on the technical reasoning of microservices. We also have to consider sociological aspects, as they can be much more dangerous than making a technical mistake.</p> <p>Thoughts?</p> <p>Oskar</p><![CDATA[How (not) to cut microservices]]>https://event-driven.io/en/how_to_cut_microservices/https://event-driven.io/en/how_to_cut_microservices/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/92be6c1698ca08b79dc8c80989b8c453/d2429/2021-01-13-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABRklEQVQoz52S20vCcBTH/auDeqk3ieqhUjLI0qDyFgU9pIIUaSmUXU3d9nMX5zZZN1qE7my/8wubmPbS6Ptw4Bz4nHuA+RaldGBxIC8S8EmOgHH5gpGiS92zYimdOczmC5Zleen+hh3HYYyVy5VEKsMYO87lt2Kx77jrwWgDgOMyHDU50a3a0YKLoaXlyGooHAqvKUr7p3LftkVFI1Jb7z5ZH58AgIiUUoBBzYOj7Mxs8OGxqapavcGBDb9nBoCX13dR0av3/HWtdVuXaoJKZLlj6BvxvVy+cHJaTCTT0c14ZD2aTO0bhjGEbQDz+U3VTL6lNjiZE2RRVo2u2TXNlijFd5LzCytT03M3N3eEEJ4Xzi8qse1dXwvzxtZ0o1gqNzlBFKXLq2q9wU2cCpHhuIYgUkTXdRFZr9eXlTYhoneq/z+J534BtVFnky3zZk8AAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/92be6c1698ca08b79dc8c80989b8c453/a331c/2021-01-13-cover.png" srcset="/static/92be6c1698ca08b79dc8c80989b8c453/36ca5/2021-01-13-cover.png 200w, /static/92be6c1698ca08b79dc8c80989b8c453/a3397/2021-01-13-cover.png 400w, /static/92be6c1698ca08b79dc8c80989b8c453/a331c/2021-01-13-cover.png 800w, /static/92be6c1698ca08b79dc8c80989b8c453/d2429/2021-01-13-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Today, I’m holding a keyboard in one hand and scissors in the other. What do I need these scissors for? According to the post title, I would like to talk about cutting (micro) services.</p> <p>We’ve come a long way from the moment when the saying “you do a monolith” sounded like “you’re dumb”, to the time when many people refrain from saying the word “microservices”. How is that? In the IT world, buzzwords are popping out every day. Just like in clothes selection, fashion is an essential thing in programming. It is also interesting how initially smart ideas and patterns, chewed by the jaws of the blogosphere, can eventually become something inedible.</p> <p>I believe that we can divide misunderstandings about microservices into two categories:</p> <ol> <li><strong>Technical</strong>: the idea is reduced to the hosting itself, Docker, Kubernetes pods etc. In this (mis)understanding, a microservice is something that we deploy separately,</li> <li><strong>Sociological</strong>: cargo cult. We’re doing that because others are doing it. I saw the scenarios, where technical leaders and managers decide to use microservices because they cannot find employees. After all, candidates want to do microservices - seriously!</li> </ol> <p>Let’s focus on the technical part today. More specifically, on the <em>“deployment unit”</em>: what is it? It’s the single application hosted in one runtime environment, e.g. a web API. Yes, sometimes the deployment unit is called microservice. Is it right?</p> <p><strong>When do I think it is worth having more than one deployment unit in the application?</strong></p> <ol> <li><strong>The difference in traffic and workload in individual parts of the application.</strong> Imagine that we are working on an e-book sales system. In such a scenario, modules for managing and adding e-books will have less traffic than the item search engine or shopping module. For such a case, it’s worth deploying search and management modules separately. It might be worth having a larger machine for the search engine, a tiny one for the management panel, and medium one for shopping. Such a split will allow us to utilize resources better and thus save money.</li> <li><strong>Increasing the throughput.</strong> There are scenarios where we need dynamic scaling. For example, in the mentioned e-book system on Black Friday or Boxing Day, we may experience high “peak” traffic. It might be worth scaling horizontally by adding additional system instances. They could be clones of the same system version accessing the same resources (e.g. the same database). Both system and storage should be prepared for that. For example, clones should be stateless because one clone knows nothing about the others and will not be able to share the state.</li> <li><strong>Multi-tenancy.</strong> Usually we are selling the same system to different customers (tenants). In that case, it is worth ensuring that the changes made by one customer don’t affect another client operations. Data isolation is the key area that we need to take into considerations. Besides that, different customers may have different traffic. We may face the scenario when one client has more traffic than the others, or something unforeseen has happened to them (e.g. suddenly increased demand, DDoS ​​attack, etc). We want to avoid a scenario when that impacts other customers. Especially if we have competing companies as customers and one is affecting the throughput of the other. To achieve that, we may need to create separate system instances per customer (both data and network isolation). This approach is called sharding. In short, with this approach we keep independent clones of our system per customer.</li> <li><strong>Multi-region.</strong> Nowadays, quite often, our applications must work in multiple geographic regions. Many people do not realize it, but we have thick optical fibres pulled across the bottom of the oceans. The Internet is transmitted through them between continents. We don’t think about it every day, but physical distances also matter. Therefore, we need to make sure that our application is efficient not only in our geographic area, but also in others. We can do that by cloning the instances across regions and putting them close to the users’ location. What’s more, we usually have to conjoin that with the considerations described in previous points.</li> </ol> <p><strong>OK, so which technical reasonings about microservices I found unfortunate?</strong></p> <ol> <li><strong>The necessity of having 100% availability of our application.</strong> It would be nice to have a “rolling deploy”, i.e. we put a copy of the application as a separate instance running in parallel. We switch the traffic to it and turn off the old version. However, in practice, after over 12 years of my career, I have never worked in a system where the business would not agree to a reasonable service window. In typical IT systems aiming for 100% availability is not worth the effort. It complicates the development and operations architecture. It’s proven that we can do this today, but we should ask ourselves “why?“. So let’s first make sure that it is indeed a must-have for our client.</li> <li><strong>The need to have different microservice per type of storage.</strong> In the past (?) database was usually placed together with the application server. In today’s era of clouds and Dockers, our database is generally hosted in a completely different location than our application. If our system uses two databases (e.g. Postgres for most data, Elastic for searching), do we need to have separate microservices per storage type? If the traffic is the same in every part of it and the cases described above do not apply, why to cut it? What benefit would we get out of that?</li> </ol> <p>What is your opinion on this? I am happy to discuss that in the comments.</p> <p>I’m looking forward to your opinion!</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Bring me problems, not solutions!]]>https://event-driven.io/en/bring_me_problems_not_solutions/https://event-driven.io/en/bring_me_problems_not_solutions/<p><strong><em>“Bring me solutions, not problems!”</em></strong> I’ve heard this sentence multiple times from Business and management. You’ve heard it too, haven’t you?</p> <p>We should shout back <strong><em>“Bring me problems, not solutions!”</em></strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 680px; height: auto" > <a class="gatsby-resp-image-link" href="/static/52e261c0c74a483c78b361c46f0bf30f/fc6d8/2021-01-06-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 64.5%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAEDBP/EABUBAQEAAAAAAAAAAAAAAAAAAAEC/9oADAMBAAIQAxAAAAFGNzVyon//xAAcEAABBAMBAAAAAAAAAAAAAAADAAECEQQSFBP/2gAIAQEAAQUCbKLXUW+4yI+kQxt/OC//xAAWEQEBAQAAAAAAAAAAAAAAAAAAARH/2gAIAQMBAT8BrH//xAAVEQEBAAAAAAAAAAAAAAAAAAAAEf/aAAgBAgEBPwGq/8QAGhAAAgIDAAAAAAAAAAAAAAAAAAEhMgIRQf/aAAgBAQAGPwJVkqmVQtdFJOKZ/8QAGxABAAMBAAMAAAAAAAAAAAAAAQARIUFRYbH/2gAIAQEAAT8huleinIdwcAuVZ9Yqm314jtvbE3U6z//aAAwDAQACAAMAAAAQKO//xAAVEQEBAAAAAAAAAAAAAAAAAAABEP/aAAgBAwEBPxABH//EABYRAQEBAAAAAAAAAAAAAAAAAAEQQf/aAAgBAgEBPxAWx//EAB0QAQEAAgEFAAAAAAAAAAAAAAERACExQWGRobH/2gAIAQEAAT8QCcrkI+2TncgaZ2uUqqMaaPnCkHLWxODIHOol+4kqVU1XP//Z'); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="meme" title="meme" src="/static/52e261c0c74a483c78b361c46f0bf30f/fc6d8/2021-01-06-cover.jpg" srcset="/static/52e261c0c74a483c78b361c46f0bf30f/37402/2021-01-06-cover.jpg 200w, /static/52e261c0c74a483c78b361c46f0bf30f/4cda9/2021-01-06-cover.jpg 400w, /static/52e261c0c74a483c78b361c46f0bf30f/fc6d8/2021-01-06-cover.jpg 680w" sizes="(max-width: 680px) 100vw, 680px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Imagine you’re working on the HR system: holidays, contracts, that sort of thing. “Business” comes to you and says:</p> <p><strong><em>“Listen, let’s make a web app where employees will be recording their working hours. We talked in the kitchen lately about those “Reacts” and “Angulars” of yours. We could use them to make it look good. Will you do it?”</em></strong></p> <p>So the planning begins: severe discussions about whether we should put the form here or a grid there. Fierce negotiations with the backend, whether we are doing a rest API or maybe GraphQL. Whether a contract should look like this or the other way discussed in a separate meeting.</p> <p>After the fights, in the end, it turns out that we even managed to complete the project on time, even with the requirements in Jira (woohoo!). The system has only a few bugs. We needed multiple sprints, but the job is done!</p> <p>If it is so beautiful, why is it so bad?</p> <p>It turns out that system users are not happy to type in their working hours, manually, every day. Previously, they entered their hours into an Excel sheet and sent them to their supervisor once a week. The supervisor then mixed it into one large Excel file and sent it on to the HR department.</p> <p>These unhappy users will nag you to add or correct something in the system you have cleverly designed to solve the problem presented to you. In such a situation, where an unhappy user meets a solution they do not like, there will eventually be a confrontation. Confrontations can be painful, but they can also be helpful; they can expose the real problem that needs to be solved, rather than the solution handed to you by someone else. <strong>In this situation, it turned out that the user’s ability to fill out their time sheets wasn’t the actual problem to solve.</strong></p> <p>It was the solution to the problem, as decided by someone else. The real problem was that the HR department needs to know how many days each person worked to verify the schedules and calculate the fees.. The perpetually busy Team leaders, buried with their other work, were late putting these Excel files together. They also treated it as a tedious duty and made a lot of copy/paste mistakes. The staff also wasted time verifying this data and prosecuting them later to correct the entries’ errors.</p> <p><strong>If we were to ask what the actual problem is, we might conclude that you may not need to create a new web application.</strong> Maybe you don’t need to make new APIs; perhaps it would be enough to:</p> <ul> <li>create a collective email account for sending and receiving the Excel spreadsheets,</li> <li>integrate these with the e-mail system, grab emails from that account, import attachments and parse them,</li> <li>if the Excel did not contain errors, the data will be saved to the HR system. After that, it will send the answer with confirmation to the employee,</li> <li>if the email was incorrect (e.g. it did not contain an attachment, it was impossible to parse it or the entries were wrong), the system will send an email with a request to correct it.</li> </ul> <p>It might turn out to be much more plausible, faster to do and more convenient for each party.</p> <p>We programmers usually have <strong>two attitudes</strong>:</p> <ol> <li><em>“Business won’t be telling us how to write code”</em>: we are Programmers with a capital P, and we know everything best.</li> <li><em>Business knows how the system is supposed to work</em>: I won’t ask for clarifications. I’ll code what I was asked to.</li> </ol> <p>Both attitudes can be considered irresponsible and dangerous.</p> <p>As programmers, we know technology: that’s why we’re being paid. Therefore, “business” shouldn’t come and tells us what framework to use, or how to create API endpoints. Of course, assuming that the application with our design:</p> <ul> <li>does what it should,</li> <li>is delivered on time,</li> <li>maintenance costs are roughly the same as an alternative solution,</li> <li>it does not increase project risks (e.g. higher development costs).</li> </ul> <p>The technical decisions should be entirely ours. Of course, the consequences of our choices should also be ours.</p> <p>However, we must remember that we are not business specialists, even if we are a well-paid profession. Let’s make it clear: we are well-paid artisans, not artists. We are supposed to do our work to help the Business efficiently earn money. We are not specialists in the business processes in a given field.</p> <p>But!</p> <p>We cannot assume that Business knows everything. If our business domain is HR and payroll, will the “Business” understand what the authorization administration panel should look like? Or will they know what options we have to integrate with external systems? Will they know what the login screen should look like? Or which form will be prettier?</p> <p>Maybe they will, but most likely they will honestly not know it, because it is not the “core” business domain. Business people are specialists in, e.g., human resources and payroll. They will be probably guessing how other features should work.</p> <p><strong>It is often the case that “Business” wants to help us solve a problem and automatically suggest a solution.</strong> When we ask <em>“why should it be like that?”</em> then we will often get the answer <em>“I don’t know, I thought it would make it easier for you.”</em>.</p> <p>I recently had such a case, when my Product Owner wanted us to block the record’s use for all users when some background operation is working on it.</p> <p>We have to be proactive and ask to determine if we’re getting a description of the actual problem or just the solution presented to us by someone else (a potential business solution). When we’re trying to understand a business process, we do not question its validity. It is, de facto, our duty to help the Business solve the problem. By taking the requirements blindly, we do not help them at all: we can even make it worse!</p> <p>I predict that the time when software companies and business companies were separate things will soon be gone. It is a relic of the past where IT systems duplicated what people were doing to make it faster. Now with growing SasS solutions, IT is Business. Therefore, close cooperation between Business and IT is no longer “nice to have”,​​it is a “must-have”.</p> <p>Henry Ford once said:</p> <p><strong><em>“If I had asked people what they wanted, they would have said faster horses.”</em></strong></p> <p><strong>I’ll leave some homework for you. Consider whether the following cases are a problem or a solution?</strong></p> <ul> <li>licensing system in SASS systems</li> <li>user administration panel</li> <li>authentication and authorization</li> <li>unique invoice number</li> <li>development of a shipment handling system</li> </ul> <p>Let me know what came out of it, and what are your thoughts!</p> <p>Cheers!</p> <p>Oskar</p><![CDATA[Outbox, Inbox patterns and delivery guarantees explained]]>https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/https://event-driven.io/en/outbox_inbox_patterns_and_delivery_guarantees_explained/<p>Yesterday I was asked by Cezary about the transactional outbox pattern sample implementation.</p> <blockquote class="twitter-tweet" data-dnt="true"><p lang="en" dir="ltr">Looking for an example implementation of Transactional Outbox Pattern with <a href="https://twitter.com/hashtag/SqlServer?src=hash&amp;ref_src=twsrc%5Etfw">#SqlServer</a> Transaction log tailing in <a href="https://twitter.com/hashtag/dotnet?src=hash&amp;ref_src=twsrc%5Etfw">#dotnet</a>. Any existing libraries for that?<a href="https://t.co/S93CojFVER">https://t.co/S93CojFVER</a><a href="https://t.co/HhkMfAqYdN">https://t.co/HhkMfAqYdN</a><br><br>cc: <a href="https://twitter.com/spetzu?ref_src=twsrc%5Etfw">@spetzu</a> <a href="https://twitter.com/d_pawlukiewicz?ref_src=twsrc%5Etfw">@d_pawlukiewicz</a> <a href="https://twitter.com/kamgrzybek?ref_src=twsrc%5Etfw">@kamgrzybek</a> @oskar_at_net<br><br> <a href="https://twitter.com/hashtag/microservices?src=hash&amp;ref_src=twsrc%5Etfw">#microservices</a></p>&mdash; 🛠 Cezary Piatek - Automation Tycoon (@cezary_piatek) <a href="https://twitter.com/cezary_piatek/status/1344016764246642688?ref_src=twsrc%5Etfw">December 29, 2020</a></blockquote> <p>My answer was:</p> <p><a href="https://twitter.com/oskar_at_net/status/1344184247100329985">https://twitter.com/oskar_at_net/status/1344184247100329985</a></p> <p>The question was short - answer not so much.</p> <p>Before I answer that let’s start with fundamentals. In distributed environments, we have three main delivery guarantees:</p> <ul> <li><strong>At-most once</strong> - this is the simplest guarantee. You can get it even with in-memory / in-process messaging. When we send a message and processing fails then the message will be lost and not handled. That could happen both for transient and not transient errors. It can be a temporary database outage, losing network the packet, the server is down or not working. The advantage of this is that we do not have to deal with idempotence, but the downside is that we have no guarantee of message delivery.</li> <li><strong>At-least once</strong> - having this guarantee, we are sure that sent message will always be delivered. However, we are not sure how many times it will be handled. We can achieve that by re-publishing messages on the producer’s side (e.g. with an outbox pattern) or re-handling on the consumer side (e.g. with an inbox pattern). The disadvantage is that the message can be handled several times. We have to defend ourselves through correct idempotency handling. If we don’t do that, we may end up with corrupted data (e.g. we will issue an invoice several times). For example, this can happen when the invoice was saved to the database, but operation timed out. If we retry processing without proper verification if such an invoice already exists, we may duplicate it.</li> <li><strong>Exactly-once</strong> - this semantic guarantee that sent a message will be handled exactly once. It is very difficult (sometimes even impossible) to achieve, as processing may fail on the multiple stages. If we want to have such guarantee then besides the retries we need to have correct idempotency support. We need to make sure that operation performed several times won’t cause side effects.</li> </ul> <p><strong>Let’s try to explain the funky word “Idempotency”</strong>. When the operation is idempotent, it will leave the system in the same state no matter how many times it was performed. If we call it multiple times - the result will be the same. We can achieve that, for example, by sending a unique message identifier. Based on that, we can perform deduplication. If our storage allows transactions, we can store the processed message ids and put a unique constraint on them. If we perform the database change in the same transaction as storing message id then our database will make sure that our operation is idempotent. See more in <a href="https://developers.eventstore.com/clients/dotnet/20.10/appending/optimistic-concurrency-and-idempotence.html#optimistic-concurrency-and-idempotence">EventStoreDB idempotency documentation</a>.</p> <p>The downside is that we always have to perform the check and store an additional record. Plus our storage has to support transactions. Such logic may also have performance degradation.</p> <p>The other option is verification by the business logic (e.g. checking whether an invoice for a given reservation has already issued).</p> <p>We should also check if we need more sophisticated idempotency handling. If we’re doing only update or upserts then they’re idempotent by design.</p> <p>To properly handle at-least-once and exactly-once delivery, following patterns should be used:</p> <ul> <li> <p><strong>Outbox Pattern</strong> - it ensures that a message was sent (e.g. to a queue) successfully at least once. With this pattern, instead of directly publishing a message to the queue, we store it in temporary storage (e.g. database table). We’re wrapping the entity save and message storing with the Unit of Work (transaction). By that, we’re ensuring that the message won’t be lost if the application data is stored. It will be published later through a background process. This process will check if there are any unsent messages in the table. When the worker finds any, it tries to send them. After it gets confirmation of publishing (e.g. ACK from the queue), it marks the event as sent.</p> <p>Why does it provide at-least-once and not exactly-once? Writing to the database may fail (e.g. it will not respond). When that happens, the process handling outbox pattern will try to resend the event after some time and try to do it until the message is correctly marked as sent in the database.</p> </li> <li> <p><strong>Inbox Pattern</strong> - it is similar to Outbox Pattern. It’s used to handle incoming messages (e.g. from a queue). Accordingly, we have a table in which we’re storing incoming events. Contrary to outbox pattern, we first save the event in the database, then we’re returning ACK to queue. If save succeeded, but we didn’t return ACK to queue, then delivery will be retried. That’s why we have at-least-once delivery again. After that, an outbox-like process runs. It calls message handlers that perform business logic.</p> <p>You can simplify the implementation by calling handlers immediately and sending ACK to the queue when they succeeded. The benefit of using additional table is ability to quickly accept events from the bus. Then they’re processed internally at a convenient pace minimising the impact of transient errors.</p> </li> </ul> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/614379d9263d1b1395bf8ad305047ed3/a331c/2020-12-30-outbox.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAB4UlEQVQozzWSW2sUQRCF5///EkEhL4I+CIIvERGMJtlkd3buM93VVdVdfZme2RhhZUaEQz3WqfOdKnxcySWwEchHdfcC3x1rkmzYA0e0SeLi85/A42W6FTgrjOSS89lKLoCj5kguA8uKx6tUBD3wPBqpBj52pNGjrA6aq5SJOk0BOKCNJHOBLpFk61dycYX7qz2g7tmvnXIPZ7orsdfeuEWgvtJdogbdTG42FNClgiSjzCSZZJbuNrWfmQyHy0TYaqhGgywcXlhXqf7o1AEls5/JJXKpcGGxfmGfXVhHA934JHDmkAdVMeneTAiNi2s7PPdTPZFxYbVx4c1vLqzPm0KWdBkttdCJs+XAh2r0cQaWzkynwR6rFqyZ7BaOfUab0M0bsH/SHOqTqo6T0t6gfy57jd7YOCijwT0+NOVhaBowbqsA7TYLlLwzm9Gm80NX3Q9TB3/wm6fR2IUkM/tX81WVZfmzb0plQ96TZpa5oG1NMnZ3LlV56LsesH9SSmlOQBGMU/WP+lidHvuuNWCjsQn3FyhqLQOwsd6GHJfXx2P15u3Nh09f2GfZ4s0h//51qN/dvL9/OqXL6+YZlv3yVLQYJiOGI4eF40o+887PxsXG1aWLTxeJi8R15/xffiG//AWYaVrvYvjAXAAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/614379d9263d1b1395bf8ad305047ed3/a331c/2020-12-30-outbox.png" srcset="/static/614379d9263d1b1395bf8ad305047ed3/36ca5/2020-12-30-outbox.png 200w, /static/614379d9263d1b1395bf8ad305047ed3/a3397/2020-12-30-outbox.png 400w, /static/614379d9263d1b1395bf8ad305047ed3/a331c/2020-12-30-outbox.png 800w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Both Outbox and Inbox can be implemented with polling or triggered by the change detection capture.</p> <p><strong>For polling implementation</strong> details you can check a detailed <a href="http://www.kamilgrzybek.com/design/the-outbox-pattern/">post by Kamil Grzybek</a>.</p> <p>TLDR. You store events in such table:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> app<span class="token punctuation">.</span>OutboxMessages <span class="token punctuation">(</span> <span class="token punctuation">[</span>Id<span class="token punctuation">]</span> UNIQUEIDENTIFIER <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>OccurredOn<span class="token punctuation">]</span> DATETIME2 <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token keyword">Type</span><span class="token punctuation">]</span> <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token keyword">Data</span><span class="token punctuation">]</span> <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span>MAX<span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>ProcessedDate<span class="token punctuation">]</span> DATETIME2 <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">CONSTRAINT</span> <span class="token punctuation">[</span>PK_app_OutboxMessages_Id<span class="token punctuation">]</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">[</span>Id<span class="token punctuation">]</span> <span class="token keyword">ASC</span><span class="token punctuation">)</span> }</code></pre></div> <p>Then (as described above) you’re processing events in the order of occurrence. After you processed them, you update their <em>ProcessedDate</em>.</p> <p>That’s a decent option for the regular solution. Sometimes we have higher throughput needs. To allow load balancing and processing in parallel or by multiple outbox consumers it could be updated to e.g.</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> app<span class="token punctuation">.</span>OutboxMessages <span class="token punctuation">(</span> <span class="token punctuation">[</span>Id<span class="token punctuation">]</span> UNIQUEIDENTIFIER <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>OccurredOn<span class="token punctuation">]</span> DATETIME2 <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token keyword">Type</span><span class="token punctuation">]</span> <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span><span class="token keyword">Data</span><span class="token punctuation">]</span> <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span>MAX<span class="token punctuation">)</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>EventNumber<span class="token punctuation">]</span> <span class="token keyword">int</span> <span class="token operator">NOT</span> <span class="token boolean">NULL</span> <span class="token keyword">AUTO_INCREMENT</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>PartitionKey<span class="token punctuation">]</span> <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token keyword">CONSTRAINT</span> <span class="token punctuation">[</span>PK_app_OutboxMessages_Id<span class="token punctuation">]</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">[</span>Id<span class="token punctuation">]</span> <span class="token keyword">ASC</span><span class="token punctuation">)</span> }</code></pre></div> <p>and adding table:</p> <div class="gatsby-highlight" data-language="sql"><pre class="language-sql"><code class="language-sql"><span class="token keyword">CREATE</span> <span class="token keyword">TABLE</span> app<span class="token punctuation">.</span>OutboxConsumers <span class="token punctuation">(</span> <span class="token punctuation">[</span>Id<span class="token punctuation">]</span> UNIQUEIDENTIFIER <span class="token operator">NOT</span> <span class="token boolean">NULL</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>LastProcessedEventNumber<span class="token punctuation">]</span> <span class="token keyword">int</span><span class="token punctuation">,</span> <span class="token punctuation">[</span>PartitionKey<span class="token punctuation">]</span> <span class="token keyword">VARCHAR</span><span class="token punctuation">(</span><span class="token number">255</span><span class="token punctuation">)</span> <span class="token boolean">NULL</span> <span class="token keyword">CONSTRAINT</span> <span class="token punctuation">[</span>PK_app_OutboxMessages_Id<span class="token punctuation">]</span> <span class="token keyword">PRIMARY</span> <span class="token keyword">KEY</span> <span class="token punctuation">(</span><span class="token punctuation">[</span>Id<span class="token punctuation">]</span> <span class="token keyword">ASC</span><span class="token punctuation">)</span> }</code></pre></div> <p>This change makes possible parallelisation. We no longer update the event after processing. We’re updating the single row in the Outbox Consumers with the last processed event number. Thanks for that:</p> <ul> <li>We’re getting performance optimisation, as it’s faster to update single row than multiple (e.g. doing batching in outbox),</li> <li>It allows for easier locking and implementing <a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/competing-consumers">competing consumers</a>. See also <a href="https://jeremydmiller.com/2020/05/05/using-postgresql-advisory-locks-for-leader-election/">post by Jeremy D. Miller</a>.</li> <li>We can introduce partitioning or sharding and have separate consumers for different partitions. Each consumer will have its entry in <em>OutboxConsumers</em> table differing by <em>PartitionKey</em>. The partition can be, e.g. events stream from single aggregate, events from the module, etc. What’s important to note is that we get the guarantee of ordering for the events within the same partition - so it’s better to consider strategy carefully. See also <a href="https://martendb.io/documentation/documents/tenancy/">Marten tenancy documentation</a>.</li> <li>Have more than a single consumer (e.g. one for webhooks, one for the queue, etc.)</li> </ul> <p><strong>Change detection capture-based (or also called transactional) takes that on a different level</strong>. Polling will always have redundancy, as the background workers need to call database for new events continuously. Almost all popular databases provide functionality for getting triggers when data was changed, e.g.</p> <ul> <li><a href="https://www.postgresql.org/docs/9.0/wal-intro.html">Postgres WAL</a>, <a href="http://www.npgsql.org/doc/replication.html">Npgsql WAL support</a>,</li> <li><a href="https://docs.microsoft.com/en-us/sql/relational-databases/logs/the-transaction-log-sql-server?view=sql-server-ver15">MSSQL transaction log</a>,</li> <li><a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Streams.html">DynamoDB change streams</a>,</li> <li><a href="https://learn.microsoft.com/en-us/azure/cosmos-db/change-feed">Change feed in Azure Cosmos DB</a>,</li> <li><a href="https://developers.eventstore.com/clients/grpc/subscribing-to-streams/">EventStoreDB subscriptions</a>.</li> </ul> <p>We could use such triggers for outbox processing. Instead of the background process, we get notifications on new events. We can also parallelise processing with routing to different handlers by partition key.</p> <p><strong>I described that in more details in <a href="/en/push_based_outbox_pattern_with_postgres_logical_replication">Push-based Outbox Pattern with Postgres logical replication</a>.</strong></p> <p>To sum up. Outbox and Inbox patterns are must-haves for getting at-least once or exactly-once delivery. They’re used internally in many solutions like</p> <ul> <li><a href="https://debezium.io/blog/2019/02/19/reliable-microservices-data-exchange-with-the-outbox-pattern/">Kafka Connect with Debezium</a>,</li> <li><a href="https://docs.particular.net/nservicebus/outbox/">NServiceBus</a>,</li> <li><a href="https://jasperfx.github.io/documentation/durability/">Jasper</a>,</li> <li><a href="https://masstransit-project.com/articles/outbox.html">MassTransit</a>.</li> </ul> <p>If you’d like to make your message delivery predictable and successful - you should consider applying that to your system.</p> <p><strong>I hope that this post helped you! As always - comments are more than welcome!</strong> If you’d like to continue investigation around distributed processing, also read my article <a href="/en/saga_process_manager_distributed_transactions/">“Saga and Process Manager - distributed processes in practice”</a>.</p> <p>Oskar</p> <p>p.s. I’m thinking about writing library focused on Outbox and Inbox pattern. Please add your comment if you’ll be interested</p><![CDATA[How to (not) do the events versioning?]]>https://event-driven.io/en/how_to_do_event_versioning/https://event-driven.io/en/how_to_do_event_versioning/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/3824cfd6f17fe648fc783145a5e8bf8b/d2429/2020-12-23-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACd0lEQVQozxXMO2/TQAAAYE8gBl5tATHQAhIrAgmpAxMjA1MLXdmYYAa2MjEghEQphVKVVm3qJI3tsx3bZ59fcer4zo/YSVSauClOH0k6oS7AiPh+wEf9+fu73z/sdlOMXYRUz3O3t5txUg9D3/dxHNejKEiSetKoBwGpODbGNYy9OI5arSa1v59VqzZCmmUZHChZlqmqsqyUN0sFXuB4gWNBSYES4FleZBUouW41DP16PWy1GtRR/6DbTcMoiOOo5laJjzHxPM+tOLZh/h9t27Rt07INjF0/JEEYuO6WH+Bmq0GdnPw66h/09rNuN3XdKiFeEBKMa45TcRzbrlimpRPfi+oBxjXiezXPm5qeWVtfSRoJNRj2f2Z7jWYSRWHgkzAklqVblg5VWdWgJAkKLCNdQ7rGAUbX1RxNnzp9ZnLyHgcY6vh4MBgcpmk7acTY8yzLkOWyqiqmpSNdLUviem5NQ1DVFI5jBBEsLi2dvzh65eqlfGGdGg6HWba3u9tpNpuGaQgCj5Bq6Lrj2E61UijSxWK+xBQ26Nx6Llco5he+fjt7bmTs8tjsm1mq1+uluykhBAAgCLymQVHkaTonijxCiAOsaRmKIucL9Aa9kS/Qb9+9n7h569rE+KvXL6ksyzqddqfdxl6tUrG3qlVVVXgeAB6IosAyjCDym5tFWZY4ABi2NP9lcfL+g9t378x/nqc6nXTnx45PiOM4CCFFUTQVSlJZFHieB4IgiALPsgzLAZbjIJSXV3MPH02PX78x9WSaajVbURh22u1GkhCCPa9mmgaEECqKpmkI6RBCWVGWvy/PfZpbWV358HHh8czTCyOjz188+we+ftGsYJcM9QAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/3824cfd6f17fe648fc783145a5e8bf8b/a331c/2020-12-23-cover.png" srcset="/static/3824cfd6f17fe648fc783145a5e8bf8b/36ca5/2020-12-23-cover.png 200w, /static/3824cfd6f17fe648fc783145a5e8bf8b/a3397/2020-12-23-cover.png 400w, /static/3824cfd6f17fe648fc783145a5e8bf8b/a331c/2020-12-23-cover.png 800w, /static/3824cfd6f17fe648fc783145a5e8bf8b/d2429/2020-12-23-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Developers that are not running a system in production might call themselves lucky. They can live in their perfect world. It is a world where there are no problems; everything is black and white. It is a world where you can debate whether it is better to use one pattern or another. Production changes everything. Until a man finds himself in such a situation, it isn’t easy to imagine it. Once we’re there and hear “Houston we’ve got a problem”, all our academic discussions become almost irrelevant. We have to make rotten compromises, which we are often ashamed of afterwards. Of course, we’re creating stories that we can tell our friends over the beer, but not necessarily during the interview. If there were a thriller movie about development, it would start at 4 PM on Friday with the hotfix on production.</p> <p><strong><em>“How do you run Event Sourced system on production?”</em></strong></p> <p><strong><em>“How do you version events?”</em></strong></p> <p>Those are the questions I’m asked quite often, and they’re challenging questions to answer. Greg Young wrote a book about it (which I recommend to everyone <a href="https://leanpub.com/esversioning/read">https://leanpub.com/esversioning/read</a>).</p> <p><strong>There are many ways to deal with versioning:</strong></p> <ul> <li>Data migration</li> <li>Extending the current event schema in a non-breaking manner e.g. adding a new field</li> <li>Adding a new event schema with a changed name or namespace</li> <li>Upcasting: plugging a middleware between the deserialization and application logic. Having that, it’s possible to transform or map old event schema into the new one</li> <li>Publishing the event in both schemas. Then, listeners who need the old one will continue working until they switch to the new version.</li> </ul> <p>Each of these practices has pros and cons. Some will work better in one situation, and worse in others. <strong>However, everyday I’m more convinced that the best option for versioning the event schema is to prevent conditions in which versioning is needed.</strong></p> <p>Am I joking? Not this time. Let’s think for a moment about when we need versioning. It’s required when we add a new schema, but still have to use the old one. If we have events in the old schema in the event store (database), it’s appropriate to handle them, and this is usually associated with adding additional code. We need to support the old code to support the old schema, and support code that is part of the new business logic.</p> <p>Migration of events is technically possible, but as we know, it’s not recommended by definition. Events should be immutable. Of course, the problem is related to old events that we still need to use. It would be great if we could get rid of them somehow, but how to do it?</p> <p>There are several ways:</p> <ol> <li><strong>Summary Event</strong>: This is a snapshot event. It has the current state of the aggregate/events stream. We can append such an event and treat it as the checkpoint, or a new starting point of our stream. Thanks to this, we can ignore the events that happened before it. We are publishing a new snapshot, so from now on, all events are published using the new schema. We could even archive the old ones. What are the disadvantages of this approach? If the summary event is just a snapshot, then it won’t have all the business information. If we were to archive the old events, we might have problems rebuilding the projection. Of course, you can have several such events for different occasions, but still, this is quite a rare and obscure situation.</li> <li><strong>Event stream transformation:</strong> we can take streams that have the old events schema version. We can copy events, transform them and save as a new stream. We can also add a “link event” in the new stream that will reference the old stream (Something like Redirect in HTTP). Thanks to this, we get old events migrated to the new schema. We can even archive the old ones, and we don’t have to maintain two implementations. The downside is that the chronology of events is somewhat disturbed: if we republish them, then old events appear again. Where the global order of events is crucial, this can be a problem. You can check how to do it in the Marten documentation: <a href="https://martendb.io/documentation/scenarios/copyandtransformstream">https://martendb.io/documentation/scenarios/copyandtransformstream</a>.</li> <li><strong>Keeping aggregates small:</strong> I’m not talking about the code itself. Everyone knows that less code is better than more. What I am saying here is to create short-lived aggregates. For example, take the ordering process in an online store. We could model one big order aggregate. It could handle the whole process so: adding product to cart, confirmation, payment, shipment, and finally closing. We could also look at this in a different way. Instead of one gigantic aggregate, we could create several smaller one, e.g. an order bucket, confirmed order, shipment, payment, completed order. The same is true for other cases, such as in a hotel booking. Instead of supporting the entire booking process (the guest’s stay and the accounting) as one entity, we could break it down into small aggregates. We could model it as, say, tentative reservation, confirmed reservation, guest stay, paid reservation, etc. What do we get from this? If our aggregate lives shortly, a day or two, week, then these are easier to manage. If we’re deploying new changes with the new events schema, then events with the old one will be living for at most a few days. Thanks to that, we can break our deployment into two phases. First, we deploy a version that supports both schemas and mark the old one as “obsolete”. Then, during the next deployment, we get rid of the code responsible for old events, because there are no living aggregate instances with old schemas. Of course, this is not always possible; some entities live longer, but then we can use one of the techniques described earlier.</li> </ol> <p>Versioning is not easy. The earlier we start thinking about the versioning strategy, the better. However, the best is to aim to achieve a situation when we don’t need that strategy.</p> <p>If you’re looking on the practical examples on how to practically implement Events Schema Versioning, check <a href="/en/simple_events_versioning_patterns">Simple patterns for events schema versioning</a>.</p> <p><strong>Watch also more in the webinar:</strong></p> <p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 56.49999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAAAAAAAD6AF+hNEZAAABbUlEQVQoz4WS3W4TMRCF/dqgvgGXcM1LcIOQoKpIN222m+xmf/0zc2bGacljoN1NqFQhOFdj2eez54ydzhJV+asgWIv5gAqAl1/n+7tvnz9+uHn33gGXbb5W4LkEQEQgUXkDhlk+Pb+cz2dHDFUl5hQ8JYoxiZ0guvoPfpc4/fEvdADMzETkMHvVBNUQ98cdp1qOW429aLZsZVOEGPTaVVYxy2KndelCUIiYcOm1OP7s/WZT3Lb1NiuO1bG+78GX5w7DcPDU+Ya7Hc2dsYtJzYyYnzX3sSm7otrdteWPafOlejwQpTUKAPMFMVf1Lb5+Ejsxs/OjNzVeQlI1FZvaMozN5NNTUcYp4NKYdvsRgbNIGvYkM9TJEsYaD2NBWBY1VfHjVBfd0Pbd4/dpGg5FP/VhpoS2bAuBudcJvIqxUpYvACCEwMzRpxSJiIL3KZGqOL7O+R+6pL38k3VsTVP3Q3TM/ze/QTHjYVuUT+NvFbc119IGMYMAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="webinar" title="webinar" src="/static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png" srcset="/static/5884d457fe2181ab0e490a1460ab913c/36ca5/2021-12-08-webinaresver.png 200w, /static/5884d457fe2181ab0e490a1460ab913c/a3397/2021-12-08-webinaresver.png 400w, /static/5884d457fe2181ab0e490a1460ab913c/a331c/2021-12-08-webinaresver.png 800w, /static/5884d457fe2181ab0e490a1460ab913c/8537d/2021-12-08-webinaresver.png 1200w, /static/5884d457fe2181ab0e490a1460ab913c/1a152/2021-12-08-webinaresver.png 1600w, /static/5884d457fe2181ab0e490a1460ab913c/7d442/2021-12-08-webinaresver.png 1920w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>And read in the versioning series:</strong></p> <ul> <li><a href="/en/simple_events_versioning_patterns/">Simple patterns for events schema versioning</a></li> <li><a href="/en/explicit_events_serialisation_in_event_sourcing/">Explicit events serialisation in Event Sourcing</a></li> <li><a href="/en/fun_with_json_serialisation/">Fun with serial JSON</a></li> <li><a href="/en/how_to_map_event_type_by_convention/">Mapping event type by convention</a></li> <li><a href="/en/event_versioning_with_marten/">Event Versioning with Marten</a></li> <li><a href="/en/lets_take_care_of_ourselves_thoughts_about_comptibility/">Let’s take care of ourselves! Thoughts on compatibility</a></li> <li><a href="/en/internal_external_events/">Internal and external events, or how to design event-driven API</a></li> </ul> <p>Cheers!</p> <p>Oskar</p><![CDATA[Optimistic concurrency for pessimistic times]]>https://event-driven.io/en/optimistic_concurrency_for_pessimistic_times/https://event-driven.io/en/optimistic_concurrency_for_pessimistic_times/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/cca4af5b2b4d996406f3b9a51e0648e1/d2429/2020-12-16-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACqklEQVQozwGfAmD9AAEBAgICAgIBAQEBAQMDAwICAgYGBhYDBiMFCRsDBiQJDCIHCwsFBgECAgYGBgQEBAUFBQgICAcHBwUFBQAAAAAAAAABAgIFBgUEBQQNDQ4GBgY7HyJPICVQKi43CxA2Cg8XDQ4aHBwHBwcICAgJCAgLCggKCggLCgkAAwEBDgYGEgkIDw0NAAAAW1taiomJd3p5nqOhpqinYWVkAAAAXV5e0dHRExISCAgHDQ0LDg0LEQ4MHBUUABYOCXI+G3xAG0ssGwAAAGNiYamnp4SCgbWzsq6pqIF7ewAAAIWFhMnJyEJAPwQDARkUEhsWFCodGz0kIQAWFBM9KBhnOhhHLhoLCwsREhESDxBGNjNJOjhfTUsxIB8lGhk2KigpICAvJiUdFBImGxspHBstGxg3GxcAIxgYEA4QDw4QIxgZIBYWEAsIOB8SLxwaMhwZLxgVMh4dNx8cOiAdPCIfLxoXKh4cGA8NOC8tc21qPTo7AGNZVIOBfX1+foR+eUY0MSgbGFI9NUIgGjslIS0gHzMiIkk5OygaFzAiIUA+PxcTEB4aGXRxcO7s5LO4ugCvrp7FxLjExLvCxbt/foB0ammRmJ1XRElfUFMrJSU7IiR9c4I/Ly1VXWaZn51dXFtzen/Awb////fv7OYAt7anyce609G+oqKbh4qPnKapm6KifoSLk5+jaEpKdygtwcPNk4qPiJGWoaSdnqWoe4OOn6m77Ozq/vvyAMLBscvKu8bGvMLBsZmWjZOcoZSanpWZm7W/u46LlIx4huDn6Zuhp3d7erKyrb3J1MnMzaGns8LDxv//9wCurKDS0MK4u7nIx7ytqp+RmJmVnqOttbO0ubV7h5+qvNDx9vS3ur1/iZTBw8XQ2uHM0M7GxL3w7eL++/Q9ueUAKGngWwAAAABJRU5ErkJggg=='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/cca4af5b2b4d996406f3b9a51e0648e1/a331c/2020-12-16-cover.png" srcset="/static/cca4af5b2b4d996406f3b9a51e0648e1/36ca5/2020-12-16-cover.png 200w, /static/cca4af5b2b4d996406f3b9a51e0648e1/a3397/2020-12-16-cover.png 400w, /static/cca4af5b2b4d996406f3b9a51e0648e1/a331c/2020-12-16-cover.png 800w, /static/cca4af5b2b4d996406f3b9a51e0648e1/d2429/2020-12-16-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>Apparently, one of the worst things you can wish someone is “may you live in interesting times”. In these interesting times of ours, I wanted to write something to cheer you up, something optimistic. <strong>What could be more optimistic than an optimistic concurrency?</strong></p> <p>Why is concurrency optimistic? <strong>It assumes that this scenario, when more than one person tries to edit the same resource (a record), will be a rare occurrence.</strong> This assumption is correct for most system types. Usually, only one person at a time edits an order, ticket reservation, user data, etc. at the same time.</p> <p>Does it mean that we’re following YOLO principle, and everyone overwrites everyone? Not at all. Well, almost not at all. The first person to get to the database/service is the luckier one, because their record update will be successful. The second one fails, and is told that someone has changed this resource in the meantime. Not a very pleasant thing, but as we know, our kind of concurrency is optimistic: the assumption is that such a scenario will be infrequent..</p> <p>If we have an optimistic approach, then we should also have a pessimistic one, right? Yes, we do. How do these two approaches differ? Pessimistic assumes that there is a scheme that wants to spoil our resources, and if we just let it out of our sight for a moment, then something terrible will happen. Therefore, as a rule, as soon as we open an editing mode or open a record at all, we put a lock on it, which prevents others from editing until we graciously allow them to do so. Pessimistic locking is used, for example, in banking and other systems where finances are involved. In general, we’re taking the semaphores up or down.</p> <p>An optimistic approach has the advantage that the readings are allowed at any time; there are no restrictions here. Constraints are only for record updates (as described above).</p> <p>This approach also has an essential business feature. <strong>It assumes that we want to make decisions in our system based on the up-to-date information.</strong> If we’re going to change our resource and it has changed in the meantime, the system will ask us: “Someone updated this record. Do you really want to do it?“.</p> <p>How does the implementation of optimistic concurrency look?</p> <ol> <li>Read the current version of the resource together with the record.</li> <li>Modify the record and send it together with the (unchanged) version number.</li> <li>The server/database reads the current version of the resource.</li> <li>Check if the versions are the same.</li> <li>If not, throw an error/return the error code.</li> <li>If they are the same, allow the update, make it, and change the version (e.g. increment).</li> </ol> <p>The version doesn’t have to be a number. Very often it does have one, but not always. Why? Because there is no need to verify if the number is bigger, smaller or different by one. It is enough to check if the version is the same. In distributed systems, it is challenging to get a globally incremented number (in any case without a negative impact on performance). That is why Guid is commonly used for versioning: it allows us to assign a new random Guid as a new version. This way, we do not have to do global number synchronization, which simplifies the solution considerably. Guids are used as versions of documents in Marten (see more: <a href="https://martendb.io/documentation/documents/advanced/optimistic_concurrency/">https://martendb.io/documentation/documents/advanced/optimistic_concurrency/</a>). Entity Framework supports it as follows: <a href="https://docs.microsoft.com/en-us/ef/core/modeling/concurrency?tabs=data-annotations">https://docs.microsoft.com/en-us/ef/core/modeling/concurrency?tabs=data-annotations</a>.</p> <p>However, both EventStoreDB and Marten use numeric version numbers for event versioning (see, e.g. <a href="https://developers.eventstore.com/clients/dotnet/5.0/writing/optimistic-concurrency-and-idempotence.html#idempotence">https://developers.eventstore.com/clients/dotnet/5.0/writing/optimistic-concurrency-and-idempotence.html#idempotence</a>.). Those numbers are reused from the event sequence number (the number of occurrences in the events stream).</p> <p><strong>Especially in distributed systems, optimistic concurrency shows its power</strong>. In such systems, it is not easy to ensure strong data consistency. Using optimistic concurrency, together with distributed processing algorithms, allows for easier handling and simulating transactions. This is how distributed databases approach it:</p> <ul> <li>DynamoDB - <a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.OptimisticLocking.html">https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DynamoDBMapper.OptimisticLocking.html</a></li> <li>CosmosDB - <a href="https://docs.microsoft.com/en-us/azure/cosmos-db/database-transactions-optimistic-concurrency">https://docs.microsoft.com/en-us/azure/cosmos-db/database-transactions-optimistic-concurrency</a></li> </ul> <p><strong>How can you handle this in the web API?</strong> The most common approach is to send a version as an Etag header (<a href="https://en.wikipedia.org/wiki/HTTP_ETag">https://en.wikipedia.org/wiki/HTTP_ETag</a>). In the case of a version conflict, the 409 status is returned (<a href="https://http.cat/409">https://http.cat/409</a>). Read more on how to do that in <a href="/en/how_to_use_etag_header_for_optimistic_concurrency/">How to use ETag header for optimistic concurrency</a>.</p> <p>Optimistic concurrency is also fundamental in ensuring the order of events in <strong>Event Sourcing</strong>. I encourage you to try going through the task from my “Self Paced Kit” as ademonstration of how this works: <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Workshops/BuildYourOwnEventStore/03-OptimisticConcurrency">https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Workshops/BuildYourOwnEventStore/03-OptimisticConcurrency</a>.</p> <p>The lack of optimistic locking is also one of the reasons why Kafka is not a tool for Event Sourcing. You can learn more here: <a href="https://issues.apache.org/jira/browse/KAFKA-2260">https://issues.apache.org/jira/browse/KAFKA-2260</a>.</p> <p><strong>Do you have any questions? Feel free to comment!</strong></p> <p>Stay healthy and optimistic!</p> <p>Oskar</p><![CDATA[Why a bank account is not the best example of Event Sourcing?]]>https://event-driven.io/en/bank_account_event_sourcing/https://event-driven.io/en/bank_account_event_sourcing/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/ae81751d9fe9dfc80addc9dddfbe8667/00da5/2020-12-09-cover.jpg" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAALABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEAQP/xAAWAQEBAQAAAAAAAAAAAAAAAAACAAH/2gAMAwEAAhADEAAAAamyZHuKWf/EABsQAAEEAwAAAAAAAAAAAAAAAAEAAhEhAxAx/9oACAEBAAEFAnOsXqARi7K//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAHRAAAgIBBQAAAAAAAAAAAAAAAAERMRAhQVJhkf/aAAgBAQAGPwJX4NKuijXjI3vMY//EABwQAAICAgMAAAAAAAAAAAAAAAERACExUWFxof/aAAgBAQABPyEIWSvIaI8Ia4sUTKpg5jBAUjueuOI25//aAAwDAQACAAMAAAAQ49//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAEDAQE/ED//xAAUEQEAAAAAAAAAAAAAAAAAAAAQ/9oACAECAQE/ED//xAAcEAEAAgMBAQEAAAAAAAAAAAABABEhMVFhQXH/2gAIAQEAAT8QcWrBeA/E5GAAFADnWIFjBtEHrMGihYNdTsFlDlpp7LAfWv2f/9k='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/ae81751d9fe9dfc80addc9dddfbe8667/c60e9/2020-12-09-cover.jpg" srcset="/static/ae81751d9fe9dfc80addc9dddfbe8667/37402/2020-12-09-cover.jpg 200w, /static/ae81751d9fe9dfc80addc9dddfbe8667/4cda9/2020-12-09-cover.jpg 400w, /static/ae81751d9fe9dfc80addc9dddfbe8667/c60e9/2020-12-09-cover.jpg 800w, /static/ae81751d9fe9dfc80addc9dddfbe8667/00da5/2020-12-09-cover.jpg 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p>While explaining the Event Sourcing, bank account balance calculation is a common starting point. <strong>I claim that even though it sounds right, then it’s not the best example to show at first.</strong></p> <p>Term <strong>“Event Sourcing”</strong> directly means that <strong>events are the source of truth</strong>. We keep the system state as a series of consecutive events. That means that if you’re modifying the state of your system, for each change you log (store) the event representing the result of the action.</p> <p>It’s like when describing your day to someone: “You know, the bus was late for me, so I was late at the job. That’s why I had to stay longer, and I was late for our meeting”. After all, we rarely say “I’m late, why to go on about it”. There must be an excuse. Similarly, it usually turns out that the business expects to build the system with the ability to tell the story of what has happened.</p> <p>Getting back to the banking example. In such a case, we record all transactions for a given account, so inflows (e.g. salaries, payments, etc.) and expenses (e.g. card withdrawals, fees, etc.). Each of these transactions:</p> <ul> <li>carries specific business information,</li> <li>occurs within a particular time,</li> <li>follows one another,</li> <li>is immutable (we cannot undo a money transfer once it has been made).</li> </ul> <p>Everything seems okay, why am I picking on it? I am not a saint myself. I also gave such examples before.</p> <p>Why am I saying then that this example is problematic? In my opinion, it’s not a typical problem solved in Event Sourcing. Providing Bank Account as an example makes it easy to accuse it of performance problems. It’s easy to generalise that the whole Event Sourcing is not efficient. Let’s try to do a small calculation. Let’s say we do three transactions a day, milk in the store, cigs in the newsagent’s, transfer from grandma. 3 times 365 = 1095. This number is the sum of the annual transactions. I have a bank account, which I set up at the age of 18. It is 17 years old now. 17 x 1095 = 18615, plus four days for leap years. What is this math for?</p> <p>In Event Sourcing, the current state of our model/entity is aggregated by applying events one by one. For a bank account, if we have earned 1000 EUR, we add it to the account balance, if we have withdrawn 257 EUR, we subtract it, and receive the final account balance of 743 EUR. Taking the example of my account, we would have to download 5840 events and then apply them one by one? Crazy! It can’t be efficient, and it won’t!</p> <p>Typically, objects in our systems do not have so many events and do not live that long. Helpdesk ticket - 2 weeks, several changes (received, verified, triaged, closed). E-commerce orders, 2-3 days and a few changes (sent, handed over to a courier, dispatched, sent to another city, etc.). Usually downloading a few simple events (even 20) is not a big deal. Most applications don’t have such high-performance needs.</p> <p><strong>However, if performance is a critical factor</strong>, then <strong>you can use</strong> some of the optimisation techniques as, e.g. <strong>snapshots</strong>. What is a snapshot? <strong>It is the state of our model at a given time</strong>, e.g.:</p> <ul> <li>The current state of the object - it can be stored, e.g. in a relational table, where each field of the is a separate column. The other option is to store it in the form of a key-value. The key is the entity identifier, and the value is, e.g. JSON. That’s how Marten is doing - see more in <a href="https://martendb.io/documentation/events/projections/">https://martendb.io/documentation/events/projections/</a>. All the document, key/value databases also apply here,</li> <li>The state at a given point in time - e.g. the account balance at the beginning of the month, we can then get, e.g. snapshot from the beginning of the month, then get all the events that happened later and apply them,</li> <li>The state after each transaction - then we get the history and all state changes of our model.</li> <li>The con of snapshot is that you need to maintain it and keep the same lifetime strategies as in the regular systems, e.g. migrations.</li> </ul> <p>Another option is to send a “summary event”, which will contain the state of the object for a given moment. What does it mean in practice? Even in the financial domain, data is kept with some cadence. Usually, such systems are interested in a specific period - e.g. billing period. Even financial data do not have to be kept forever - e.g. five years for invoices in Poland. Having that, we can send a “Finished Financial Year” event for the account. It will contain the current state and other needed information. After that, we’re free to and archive old events (e.g. move to another database from where we can get the full history on demand). Thanks to this we have complete information and can still keep the advantages of Event Sourcing. Take a look at an excellent description of Mathias Verras Verras with the “Summary Event” pattern - <a href="http://verraes.net/2019/05/patterns-for-decoupling-distsys-summary-event/">http://verraes.net/2019/05/patterns-for-decoupling-distsys-summary-event/</a>. I also encourage you to do the exercises from my “Built your own event store Self-paced kit” <a href="https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Workshops/BuildYourOwnEventStore">https://github.com/oskardudycz/EventSourcing.NetCore/tree/main/Workshops/BuildYourOwnEventStore</a>.</p> <p>To sum up. Bank Account as an example is sufficient for a basic introduction, but it can quickly derail discussion with going to early performance optimisations. You can present those two of the potential solutions, but it can make the Event Sourcing seem more complicated than it is.</p> <p>It’s also a complex domain. Explaining the boundaries and providing proper aggregates structure might be hard, e.g. how to setup aggregates (“is transaction an aggregate?”). Then next questions may appear: “How to make money transfer between accounts without updating two aggregates at once etc.”. You may end up with explaining concepts of distributed processing like Saga, Process Manager, Choreography etc. Of course, it’s valuable to explain them, but in the beginning, it may be overwhelming. Someone might get the impression that those concepts are part of Event Sourcing, while with regular approach you’ll face the same issues (if you try to model a distributed banking system).</p> <p>What could be a <strong>better example</strong>? It could be a tedious but well-known <strong>Order</strong> example or <strong>Helpdesk ticket</strong>. Another one could be the meetup of a programming group. These are things that are closer to the everyday problems solved in Event Sourcing. They do not differ at all from the typical programmer’s topics. What makes Event Sourcing different from traditional programming is what Greg Young said:</p> <p><em><strong>“When you start modelling events, it forces you to think about the behaviour of the system. As opposed to thinking about the structure of the system.”</strong></em></p> <p>Do you know better examples? Or maybe you know even worse ones? Do you agree or disagree? Feel free to comment - I’d like to know your perspective!</p> <p>Oskar</p><![CDATA[Revolution now!]]>https://event-driven.io/en/revolution_now/https://event-driven.io/en/revolution_now/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/c29a95cc47e2c1017ed5652a06e4f215/d2429/2020-12-02-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAABfElEQVQoz2NgQAU8wqKhUeFZ2Tmx8bHxiXEhYWEpaanBoRGyqkqsbKwM+IGIvIJPRHB6Qd6Bszvffrp25ebxqbMmJWdHW7tasbKxEdAsr66iY2Ucnp2ycOv0nln1/dMqVqyd6h/hpKarwc7JTkCzrrFyWJxjYl6MR6hdUlF4cn5AZW2WpYO9qo4aFzdezYyMjCLSUtpm2q5+tnbOqq5+ur6BugV5kSY2Vorqctw8HAQ0q+rImNlrtk1ut7YzEOBnkJZk6WvN3rxusruXNQsLIz7NTMzMAiLcQbHB9d0NJhZaYsKCnEwMFcUJuzdPLStNZGFhIuBnfkmZmNzMmNRgfX0ZEyOlnKyAsvJoM03xSf3VqioKENfh1CwoLVvRURsS5WRnp5ueEZSd65OX49XXWXDi8CoFeWmcmiGiqlo6sxfN2bl34869G6/eODlrXn9BTtysWX0H9q4VFxMioFlTWyczJz0tOyU5MyUo1N/D08XGztbN3dXL25kTI54BPDJrvpxc3boAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/c29a95cc47e2c1017ed5652a06e4f215/a331c/2020-12-02-cover.png" srcset="/static/c29a95cc47e2c1017ed5652a06e4f215/36ca5/2020-12-02-cover.png 200w, /static/c29a95cc47e2c1017ed5652a06e4f215/a3397/2020-12-02-cover.png 400w, /static/c29a95cc47e2c1017ed5652a06e4f215/a331c/2020-12-02-cover.png 800w, /static/c29a95cc47e2c1017ed5652a06e4f215/d2429/2020-12-02-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><strong>Did you ever get an offer that someone will pay you for what you are currently doing as a hobby?</strong> What would be your decision if that would mean leaving the project that you liked? A team of people that you built from scratch being its first member? Leave the company department that you created?</p> <p><strong>I got such offer over a month ago. I accepted it.</strong></p> <p>Yesterday I started my new job. <strong>I’ll be a full-time DevAdvocate at <a href="https://www.eventstore.com/">EventStoreDB</a>!</strong></p> <p><strong>OK but, who is DevAdvocate?</strong></p> <p>To be true - I can’t precisely tell you that. However, I can tell you how I imagine it. What will be in my duties:</p> <ul> <li>Explaining Event Sourcing, building awareness of its existence among programmers. Broadening the niche, explaining not only why it is worth using it, but keeping it pragmatic.</li> <li>Providing helpful content - blog posts, examples and tutorials. Trying to show the big picture, how to integrate Event Sourcing with other patterns and tools,</li> <li>Creating tools, maybe also libraries that will be helpful for developers. <a href="https://www.eventstore.com/">EventStoreDB</a> is not a library (like, e.g. <a href="https://martendb.io/">Marten</a>) - it is a database for events (<em>so ummm, you know event store</em>). Its API is relatively low-level, it may be worth to create OSS tools that will reduce cognitive load and boilerplate,</li> <li>Communication with developers and being their advocate to the project team. For example, <a href="https://www.eventstore.com/">EventStoreDB</a> had a bit of a false start with revolutionary version 20. It had so much breaking change that people had problems with migration. It could have been mitigated with communication or different release plans.</li> <li>Working on the documentation,</li> <li>live coding, podcasting and other ideas that will pop out.</li> </ul> <p><strong>OK, but how did I get here?</strong></p> <p>For many developers Event Sourcing is like a Nessie, most of them have heard of it, but not so many saw it. I was one of them. <strong>I started my journey with Event Sourcing over four years ago</strong>. Practical journey - before that I mostly read books, which shouldn’t count. Why? Because there was not a lot of them showing how to apply Event Sourcing practically. When my colleague said that “well, you’re doing financial system then Event Sourcing sounds like a good match” - I was curious. Then I was thrilled, as finally, I was able to use the pattern known from the books. I used <a href="https://martendb.io/">Marten</a> library. First steps were hard. I was irritated. I started to get doubts if that’s something for me.</p> <p>I’m not the type of person to easily give up. Step by step, I understood more and more. Then making mistakes and learning from them, I started to get better vision. I noticed that <a href="https://martendb.io/">Marten</a> have some missing pieces, and I began to send pull requests. I also began to interact with users on Gitter channel because I saw that they had the same struggles as I did.</p> <p><strong>Two years ago, Jeremy D. Miller invited me to become the co-maintainer. I’m currently responsible for the Event Sourcing part and second committer</strong>. My current attitude is summed up best by Greg Young quote: <em>“When you start modelling events, it forces you to think about the behaviour of a system. As opposed to thinking about the structure of the system”</em>.</p> <p>I also started to give more often talks on conferences, local meetups and webinars. I created the popular repository “Event Sourcing in .NET” that contain lots of samples, exercises and materials for doing Event Sourcing in .NET - <a href="https://github.com/oskardudycz/EventSourcing.NetCore/">https://github.com/oskardudycz/EventSourcing.NetCore/</a>. I’m trying to continuously provide practical examples to show that Event Sourcing is much easier and much more realistic than many folks believe.</p> <p>TLDR - I hustled because I felt passionate about the practical aspect and what one can achieve with Event Sourcing and in general with Event-Driven Design.</p> <p><strong>OK, so that was my story, but how did I get the job?</strong></p> <p>I sent an email for the offer I saw by accident on Twitter. Looking at it, I thought - this is a job for me. However, I didn’t send the application right away. Why? Because I felt that it was going to be a conflict of interest with <a href="https://martendb.io/">Marten</a>. Plus, I wasn’t looking for a new job because the current one was a decent one. Yet then I realized that maybe I don’t have to give up <a href="https://martendb.io/">Marten</a>. I knew that there are people in <a href="https://www.eventstore.com/">EventStoreDB</a> who work at <a href="https://www.eventstore.com/">EventStoreDB</a> daily and contributes to other OSS Event Sourcing tools.</p> <p>I didn’t want to be a renegade - I’m a loyal person. I’m thankful for the chance I got from Jeremy D. Miller.</p> <p>Luckily Mat McLoughlin was fine with that and Jeremy saw no problem with it.</p> <p>Conclusion - sometimes, a person lives in his imaginary world and tries to create problems that do not exist. It’s usually better not to assume.</p> <p>Few zoom calls and email later, I got an offer, and <strong>yesterday the revolution began!</strong></p> <p><strong>I am thrilled</strong> because for me it is a great chance to work with people like Mat and Alexey Zimarev. Not always one gets the opportunity to follow the passion. I’ll be working on a great product, gaining more knowledge and even more opportunities to share it!</p> <p>I think that having a perspective of knowing different event stores, understanding the view of the everyday developer struggles I’ll be able to present and share Event Sourcing from different angles. Do it pragmatically, as I like the most. At least - that’s the plan!</p> <p>So don’t be surprised when someone knocks on your door and asks <em>“Hello. Do you want to talk about Event Sourcing?”</em></p> <p>Was it an easy decision? In theory, it was a no-brainer. In practice, it is difficult to leave the company that you have grown into. I spent almost ten years in Euvic. The company that during that time changed its name from LGBS, and grew into one of the largest Polish programming companies. I helped build the Wrocław branch. I rose from the senior dev into the Technical Leader and Architect. What’s the most important I made a team with whom I had a great time working.</p> <p>Well. Life is the art of choices. For me, it was an offer that I couldn’t reject. Is it the right decision? I believe so!</p> <p><strong>Please keep your fingers crossed and don’t afraid to reach me!</strong> I will be grateful for that!</p> <p>Oskar</p> <p>p.s. as you know how it started, read also how it finished in <a href="/en/leaving_event_store">Why I’m leaving Event Store and getting ready for the next episode</a>.</p><![CDATA[Architect Manifesto]]>https://event-driven.io/en/architect_manifesto/https://event-driven.io/en/architect_manifesto/<p><span class="gatsby-resp-image-wrapper" style="position: relative; display: block; margin-left: auto; margin-right: auto; max-width: 800px; height: auto" > <a class="gatsby-resp-image-link" href="/static/45837c7711a82aa2ac96bb60814dc8fb/d2429/2020-11-29-cover.png" style="display: block" target="_blank" rel="noopener" > <span class="gatsby-resp-image-background-image" style="padding-bottom: 55.99999999999999%; position: relative; bottom: 0; left: 0; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAALCAIAAADwazoUAAAACXBIWXMAAA6/AAAOvwE4BVMkAAACG0lEQVQoz42SXWhSYRjH3/cc77tpFLsoagktMt2Bzd30cREF0XXXi4gogqAyWNBYbMf8bEut5pxORdr0+HE+3abn+HU8To8my1qpm7HV1kUfV7sOwsai1s1+PPD8efjf/R6AQORvUASFEO7k9myHdg1F/y2i4H8ghGCP3Bm4bRk04PceP7k/YtCNmnR6h8nmGBk36nDrQ6N10Gh8gNsfWYdx002H65ph/CpuGcAt1832W7ZJ4NQ/5zxkzBlk3WRkIpgPpaS5XCW+GPeQc16amYrM2gMVMj8vSM7KeyPDm1hhLJl/li56a6vAb3NXE6USVyjHJTGWKrIST84XOJHyUtGpYMwdYvykzIo0xxPLLeLtCtva9EmvbUzCky2CmD/8vfG5LlAbtVazuBR5+VRKLzZyqSY3+SYjp6IMT4RX5Q8JISu01im5wlSqM5m8m1uIlaqAJegfa5vi9NBG/ePSQrjmusHHkytyci1t/rb+tZ4iM667X5qfaIrxvSJeOCbM+KjLHfCHwrnaMtDrh99F6UinUuT4MuePD11uNJuNQCB76Wy9UK5TVnnsys+tLTErMhGCic76XLYkR8/4p3mBB/s79mFHu04ARKNSqdXqzoMHzl+42HfocDcAJ48p+1VHsK6Oc2dOa7VajUbd19urPqXCMKz7uBLr6fkjd5drAJA9eIYQtj8JAPQ3CAK3184FQRGoUCjQ3SAoiv4CmX/+WMVn/kUAAAAASUVORK5CYII='); background-size: cover; display: block;" ></span> <img class="gatsby-resp-image-image" alt="cover" title="cover" src="/static/45837c7711a82aa2ac96bb60814dc8fb/a331c/2020-11-29-cover.png" srcset="/static/45837c7711a82aa2ac96bb60814dc8fb/36ca5/2020-11-29-cover.png 200w, /static/45837c7711a82aa2ac96bb60814dc8fb/a3397/2020-11-29-cover.png 400w, /static/45837c7711a82aa2ac96bb60814dc8fb/a331c/2020-11-29-cover.png 800w, /static/45837c7711a82aa2ac96bb60814dc8fb/d2429/2020-11-29-cover.png 805w" sizes="(max-width: 800px) 100vw, 800px" style="width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;" loading="lazy" decoding="async" /> </a> </span></p> <p><em>“Architects are not needed anymore. Those days are gone!”</em></p> <p><em>“We’re agile - we’re not doing waterfall, so we’re not designing upfront.”</em></p> <p><em>“We have autonomous teams that drive their design.”</em></p> <p><em>“We have DevOps team.”</em></p> <p>Have you heard or made such statements? Over the years we have create an enormous set of architect subtypes - <em>Software Architect</em>, <em>Enterprise Architect</em>, <em>Solution Architect</em>, <em>Chief Architect</em>, <em>DevOps Architect</em>, <em>Junior Architect</em>. I’m sure, somewhere on the web, you will find one of those bullshit title generators for architect titles. The title architect has been so abused recently that I find it difficult to refer to myself as an architect.</p> <p>For many of us, architects are just a bunch of middle-aged white dudes sitting in their Ivory Tower. Like a Mage Guild - throwing around ideas that are out of context with the work being done. Detached from the real work at hand and the real development struggles.</p> <p>In the perfect world with the perfect teams - the “A” Teams - we may be able to adhere to the principals of the microservice and DevOps movements in that our teams are autonomous, making their own decisions. We can resigned from having architects.</p> <p>Our world is rarely perfect, usually <a href="https://www.youtube.com/watch?v=4N3N1MlvVc4">it’s a mad world</a>. Teams are struggling with Conway’s Law . Knowledge sharing rarely happens. It’s challenging to maintain consistency. Designs go off into tangents. Teams become islands onto themselves. DevOps quite often appears to be just rebranded ops team or group of people holding Continuous Integration tools</p> <p>Some of us, on up to and including the enterprise have found that breaking our development out into distinct efforts working autonomously is not always the best approach. Uber evolved from a “rewrite everything” mode to one based on <a href="https://eng.uber.com/microservice-architecture/">“Domain-Oriented Microservice Architecture”</a>. The work of Matthew Skelton and Manuel Pais provided us with the concept of <a href="https://teamtopologies.com">Team Topologies</a> to <a href="https://www.youtube.com/watch?v=haejb5rzKsM">reduce the cognitive load</a> and impact of Conway’s Law. We are discovering that autonomy may not be as much of a benefit as we once thought.</p> <p>Having said all of that, I still believe that having an architect makes sense, even within the DevOps and microservice development cultures. We might not call them architects, but they are architects none the less. From my perspective, my vision, depending on the project size and diverse needs within the project it should be a group of people rather than dependence on a single individual. A group has the ability to address many associated issues and domains without creating a bottleneck within the project. Potentially, truly allowing the development teams to work with greater autonomy without impacting the project adversely. Preventing or at a minimum reducing the impact of Conway’s law.</p> <p>Some time ago, I decided to write down the vision I foresaw for such a team of architects. I was basing this upon a project I was involved in that held many of the characteristics for which I spoke of above. Its size and scope seem to justify the need. I want to share my thoughts with you. Although, they may seem a tad bit idealistic, maybe even naïve, in my opinion they are a worthwhile endeavor to undertake, possibly even something that we should aim for. I called this writing the “Architect Manifesto”</p> <hr> <ol> <li> <p><strong>Architecture team should be either a group of people that are responsible for the shape of the system architecture or an advisory board with direct responsibility with the success of the project.</strong> In my opinion, the first option is the preferred one, as an advisory board could lead to greater project confusion and objective blur - e.g., whether the teams must or should apply the recommendation of the advisory board.</p> </li> <li> <p><strong>Architecture team should provide clear guidance and documentation</strong> (written form, diagrams etc.) into the overall architecture vision and the assumptions that influenced the decisions made in finalizing said solution. The architectural solution and all the key decisions concerning it should be transparent and openly available for all developers. The architecture team should review with the developers the architectural solution so as to ensure clarity in understanding (eg. which database is to be used, why and when to use an event queue, unit test and how to write the acceptance tests etc.)</p> </li> <li> <p><strong>Architecture team should have strong authority and mandate from the management.</strong> As company can have other strong skilled and experienced developers then it should be clearly expressed by the management and explained to developers what are the Architecture Team responsibilities. The interactions and rules should be clearly defined (so eg. said that dev teams are obliged to contact the architect team while doing the new design). That will reduce the chance for confusion and disagreements.</p> </li> <li> <p><strong>Architecture team shouldn’t block the teams’ creativity and not be a bottleneck in the design process.</strong> The development teams should be responsible for the design of the features. However, they should consult in the early phases of the design with the architecture team to ensure compliance with architecture and catch early any obvious flaws in the designs. If the development team puts off the architecture team’s design review until the final feature design then sometimes it may be too late to reject the design as development team put already too much effort into it. It can cause confusion in what is to be the design of the feature and put people on the defensive. An early review as the feature is being designed can lead to an over all clearer picture of the features intent in the overall architecture. I think that having clear recommendations and an organizations defined best practices will help the development teams to provide a feature design that is properly mitered into the architecture boundaries. If the feature to be designed is exorbitantly complex then an architect may join the development team as a member of the team, not taking a leadership role, in their design efforts to provide a hands-on working relationship.</p> </li> <li> <p><strong>Architecture team should have an official time to work on the architecture.</strong> The design needs to be constructed as a focused effort and done with patience. I think that a head/lead architect should be assigned to the architectural design work and be fully-time, dedicated contributor. The rest of the architectural team assigned to the design can be part-time, defined as to the needs of the effort, with a set commitment of time to do the work. This time allotment should include direct work with the development teams on the assigned feature work as a advisor to ensure the transfer from architectural plan to actual feature development. The work of all architecture team members should be transparent for all the development teams. The architecture team should be allocated some dedicated time, at least one day a week, for consultation for the ongoing questions that arise from the development teams or management. The architecture team should participate, as advisors, in the planning, scheduling and resource allocations of each of the features to be developed. They should be should be reviewing the current state of the design and prioritize needed changes.</p> </li> <li> <p><strong>Architecture team should work closely with management, PMs, POs to understand the product priorities</strong> (and potentially help in the shaping of them). An architecture, or a working one at minimum, cannot be created in the vacuum, without context from which to design it is not practical to build quality software. Architecture, in order to be applicable, needs to be forward thinking, without the knowledge of what is to come it is not feasible to furnish/supply durable architecture.</p> </li> <li> <p><strong>Architecture team should proactively work with the developers to understand their pain points, explain and present the architecture decisions</strong> (eg. through the meetups). They should also actively ask for constructive criticism from the developers.</p> </li> <li> <p><strong>Architecture team should be responsible for continuously reviewing the application of the designed patterns.</strong> In their role as architects and development team advisors the architecture team should review designs, verify the PRs, ensure unit testing is implemented and complete and make random checks of the codebase (eg. to catch that there are NOT multiple implementations of the Graph API code). The architecture team should also verify that features are properly documented (eg. common libraries, samples, features in the code). In this review capacity the architect responsible for a particular feature implementation along with the architecture lead should work with the development team lead(s) to discuss variants from the architecture and code base duplicity and/or potential misdirection. The architecture team should NOT play the role of code police and be a deterrent to software development</p> </li> <li> <p><strong>Architecture team should not assume that once designed architecture will be always up to date.</strong> Architecture team should reevaluate if the design assumptions are still applicable. They should make sure that architecture is evolving together with the business requirements.</p> </li> <li> <p><strong>Architecture team should also work on the business features</strong> (at least once per some period of time). Architects that are not doing any regular work on designed by them architecture tend to not understand the struggles of the development team and not catch the pain points.</p> </li> <li> <p><strong>Architecture team should make sure (together with DevOps team) that continuous integration and delivery process are fluent</strong> and it’s helping to effectively deliver the new features (so if it’s reliable, fast enough, secure, etc.). They should also work with the System Teams to understand the production systems configurations to be able to make recommendations and make sure that investigation of the production bugs is effective.</p> </li> </ol> <hr> <p>Thoughts?</p> <p>Oskar</p> <p>p.s. if you liked this article, check also <a href="/en/how_using_events_help_in_teams_autonomy/">“How using events helps in a teams’ autonomy”</a>.</p>