<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gabriel Shanahan</title>
    <description>The latest articles on DEV Community by Gabriel Shanahan (@gabrielshanahan).</description>
    <link>https://dev.to/gabrielshanahan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1913508%2F639aeab7-58e8-4e85-9653-261888045de5.png</url>
      <title>DEV Community: Gabriel Shanahan</title>
      <link>https://dev.to/gabrielshanahan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gabrielshanahan"/>
    <language>en</language>
    <item>
      <title>Taming Eventual Consistency-Applying Principles of Structured Concurrency to Distributed Systems</title>
      <dc:creator>Gabriel Shanahan</dc:creator>
      <pubDate>Tue, 05 Aug 2025 10:07:36 +0000</pubDate>
      <link>https://dev.to/gabrielshanahan/taming-eventual-consistency-applying-principles-of-structured-concurrency-to-distributed-systems-57cj</link>
      <guid>https://dev.to/gabrielshanahan/taming-eventual-consistency-applying-principles-of-structured-concurrency-to-distributed-systems-57cj</guid>
      <description>&lt;p&gt;If you've ever worked as an enterprise developer in any moderately complex company, you've likely encountered distributed systems of the kind I want to talk about in this post—two or more systems communicating together via a message queue (MQ), such as &lt;a href="https://www.rabbitmq.com/" rel="noopener noreferrer"&gt;RabbitMQ&lt;/a&gt; or &lt;a href="https://kafka.apache.org/" rel="noopener noreferrer"&gt;Apache Kafka&lt;/a&gt;. Distributed, message-based systems are ubiquitous in today's programming landscape, especially due to the (now hopefully at least somewhat tempered) microservice architecture frenzy that swept over our field during the past decade.&lt;/p&gt;

&lt;p&gt;Moving away from a &lt;a href="https://signalvnoise.com/svn3/the-majestic-monolith/" rel="noopener noreferrer"&gt;majestic monolith&lt;/a&gt; involves significant tradeoffs, all of which have been documented extensively over the years. It is well known that dealing with distributed systems is a &lt;a href="https://martinfowler.com/bliki/MicroservicePremium.html" rel="noopener noreferrer"&gt;famously painful experience&lt;/a&gt;—data is only eventually consistent, errors are difficult to trace and debug, and, perhaps most frustratingly, it gets increasingly more difficult to reason about the system as a whole. This is compounded by the organic way these systems often form—rather than being a thought-out and planned architectural decision, many start out as an ad hoc solution to a particular localized problem and then gradually snowball into a mess.&lt;/p&gt;

&lt;p&gt;Nothing I've said so far is news—everybody knows that &lt;em&gt;distributed systems are a pain&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But why?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the following posts, I want to convince you that many of the difficulties traditionally associated with distributed systems are not actually unique to distributed systems at all. In fact, they're something our industry has encountered, and solved, not once, but &lt;em&gt;twice&lt;/em&gt; before—once, around 1968, when &lt;a href="https://homepages.cwi.nl/~storm/teaching/reader/Dijkstra68.pdf" rel="noopener noreferrer"&gt;we started thinking about the problems with &lt;code&gt;GOTO&lt;/code&gt;&lt;/a&gt;, and then again more recently, around 2016, with &lt;a href="https://en.wikipedia.org/wiki/Structured_concurrency" rel="noopener noreferrer"&gt;the advent of structured concurrency&lt;/a&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's more, the solutions to both problems revolve around essentially the same idea, and it turns out that this same idea—a single, simple rule—is also applicable to how we design distributed systems. Applying that idea not only prevents many of these difficulties from ever arising, but also opens the door to features that are not readily available using current approaches, such as (but not limited to) true distributed exceptions—stack traces that span multiple services, and what amounts to stack unwinding across services. And perhaps most importantly, it makes these systems significantly easier to reason about.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;However, those discussions, while interesting and educational, are also rather theoretical, and let's be honest—that's not everyone's cup of tea. So before I give you a tour of the ivory tower, I want to stay on the ground for a little while, and show you what you get when you actually apply all that ivory business. To that end, I built &lt;a href="https://github.com/gabrielshanahan/scoop" rel="noopener noreferrer"&gt;Scoop&lt;/a&gt;, and in this post, I want to talk about what it is, and what it can do. Hopefully, that will motivate you to further explore the reasoning that led me to build it in this particular fashion, and who knows, maybe you'll even learn a thing or two along the way.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this post, I'm going to concentrate on what Scoop can do, without going into too much detail about &lt;em&gt;how&lt;/em&gt;. That will be the topic of the &lt;a href="https://dev.to/posts/implementing-structured-cooperation"&gt;subsequent post&lt;/a&gt;, which will give you a helicopter overview of how Scoop and its features are implemented, and concentrate on a few fundamental topics that I purposefully avoid talking about here. Finally, in the &lt;a href="https://dev.to/posts/framing-structured-cooperation"&gt;third and final post&lt;/a&gt;, I'll frame the core concept Scoop is built around, something I'm calling &lt;strong&gt;structured cooperation&lt;/strong&gt;, in a broader context, and show you how it's the natural continuation of an idea that has, in one form or another, been shaping our industry for over half a century.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Scoop, and what did I build it with?
&lt;/h2&gt;

&lt;p&gt;Scoop amounts to what you might call an &lt;em&gt;orchestration library&lt;/em&gt;—it helps you write business operations in distributed systems. In that sense, it is similar to, e.g., &lt;a href="https://temporal.io/" rel="noopener noreferrer"&gt;Temporal&lt;/a&gt;, or, to an extent, &lt;a href="https://www.axoniq.io/products/axon-framework" rel="noopener noreferrer"&gt;Axon&lt;/a&gt; (1). Scoop is small—it can be read cover-to-cover in a few hours, and most of the magic happens in ~500 lines of (heavily documented) SQL.&lt;/p&gt;

&lt;p&gt;The primary purpose of Scoop, at least at this point, is to convey an idea. Scoop is a POC, not a production-ready library. That being said, feature-wise, it packs quite a punch if I may say so myself, especially given how small it is.&lt;/p&gt;

&lt;p&gt;The principles upon which Scoop is built, along with the contents of these posts, are language and infrastructure agnostic, and no specific knowledge is assumed here, other than familiarity with any mainstream programming language, SQL, and a vague familiarity with MQ's and distributed systems in general (e.g., you know what a &lt;em&gt;message&lt;/em&gt; or a &lt;em&gt;topic&lt;/em&gt; is).&lt;/p&gt;

&lt;p&gt;Of course, I did need to write it in something. Scoop is written in Kotlin, on top of &lt;a href="https://quarkus.io/" rel="noopener noreferrer"&gt;Quarkus&lt;/a&gt;, and uses &lt;a href="https://postgresforeverything.com/" rel="noopener noreferrer"&gt;Postgres for everything&lt;/a&gt;. I chose Kotlin primarily because of its syntax and type system, and Quarkus since it allows using both blocking and reactive database drivers in a single application, and I wanted to write Scoop in both flavors, for educational and demonstrational purposes. However, I also wanted to target as wide an audience as possible, and tried to minimize the number of assumptions I made about what my audience might be familiar with.&lt;/p&gt;

&lt;p&gt;Therefore, I:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;don't use any fancy Kotlin features (2), apart from &lt;a href="https://kotlinlang.org/docs/extensions.html" rel="noopener noreferrer"&gt;extensions&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;deliberately chose to implement a simple MQ on top of Postgres instead of using an established MQ,&lt;/li&gt;
&lt;li&gt;try to keep abstraction to a minimum,&lt;/li&gt;
&lt;li&gt;only use Quarkus for dependency injection,&lt;/li&gt;
&lt;li&gt;don't use an ORM,&lt;/li&gt;
&lt;li&gt;write raw SQL everywhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result certainly won't win any beauty contests, and I know my JVM brothers and sisters have already fainted in horror at &lt;em&gt;"keep abstraction to a minimum,"&lt;/em&gt; but hopefully, these choices will make the idea accessible to programmers from virtually any background, and conveying the idea is all I care about.&lt;/p&gt;

&lt;p&gt;There is, unfortunately, some dancing around &lt;a href="https://github.com/FasterXML/jackson" rel="noopener noreferrer"&gt;Jackson&lt;/a&gt;, the JSON library. There isalways some dancing around Jackson.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Saga Begins
&lt;/h2&gt;

&lt;p&gt;One of the most painful consequences of moving to a distributed architecture is the impact it has on transactional bounderies—you can no longer complete an entire "business operation" within the confines of a single, traditional database transaction. There are various approaches that work around this issue, such as &lt;a href="https://en.wikipedia.org/wiki/Two-phase_commit_protocol" rel="noopener noreferrer"&gt;two-phased commits&lt;/a&gt;, but a common one is the &lt;a href="https://microservices.io/patterns/data/saga.html" rel="noopener noreferrer"&gt;saga pattern&lt;/a&gt;. In a saga, you give up &lt;a href="https://en.wikipedia.org/wiki/Atomicity_(database_systems)" rel="noopener noreferrer"&gt;atomicity&lt;/a&gt; by modelling a business operation as "a sequence of local transactions"—basically you break up the operation into a set of "steps". Each step is wrapped in a regular transaction, and messages are emitted during their execution, which trigger operations in other services.&lt;/p&gt;

&lt;p&gt;This is the approach Scoop takes. Here is an example of a saga in Scoop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// saga() actually takes a second required parameter,&lt;/span&gt;
&lt;span class="c1"&gt;// but that's one of the things I'll be glossing over&lt;/span&gt;
&lt;span class="c1"&gt;// in this article&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;myHandler&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"my-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from step 1 of my-handler!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"my-handler"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello from step 2 of my-handler!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;"What's that &lt;code&gt;scope&lt;/code&gt; thing?"&lt;/em&gt; I hear you exclaim.&lt;/p&gt;

&lt;p&gt;Don't worry about it—for now, all you need to understand is that &lt;code&gt;scope.launch(&amp;lt;topic&amp;gt;, &amp;lt;payload&amp;gt;)&lt;/code&gt; arranges for a message with &lt;code&gt;&amp;lt;payload&amp;gt;&lt;/code&gt; to be published on &lt;code&gt;&amp;lt;topic&amp;gt;&lt;/code&gt; once the (local) transaction of the step commits (3). Yes, yes, using a raw &lt;code&gt;JsonObject&lt;/code&gt; is not at all how this would be done in an actual production implementation, but that's not what Scoop is.&lt;/p&gt;

&lt;p&gt;To have this saga actually do something, you subscribe it to some &lt;em&gt;topic&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// MessageQueue is part of Scoop, you just&lt;/span&gt;
&lt;span class="c1"&gt;// inject it like any other component&lt;/span&gt;
&lt;span class="n"&gt;messageQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;myHandler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// This is how you would publish a message (with&lt;/span&gt;
&lt;span class="c1"&gt;// an empty payload) on that same topic&lt;/span&gt;
&lt;span class="n"&gt;messageQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the &lt;code&gt;subscribe&lt;/code&gt; line, whenever a message is published on &lt;code&gt;some-topic&lt;/code&gt;, the saga is run, step by step—we'll talk more about that in a second. You can subscribe multiple sagas to the same topic. You can also scale sagas horizontally—running 1 instance or 100 &lt;em&gt;just works&lt;/em&gt;, no configuration needed. Just keep in mind that each step can potentially be run by a different instance of the service (4).&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollbacks &amp;amp; Coordination
&lt;/h2&gt;

&lt;p&gt;There are two rather unique issues that arise with sagas (in general, not just in Scoop).&lt;/p&gt;

&lt;p&gt;The first is how the equivalent of a transaction rollback happen—if an error happens somewhere down the road, the transactions of the previous steps are already committed, so you can't do a traditional rollback. Instead, you basically need to write code that manually undoes whatever changes you made in each step, usually called a "compensating action", and when an error happens, arrange for this code to run. I'll show you how that's done in Scoop in a moment.&lt;/p&gt;

&lt;p&gt;The second is how you coordinate the steps, since you ideally only want to run a subsequent step when the previous one has finished. This is actually trickier than it sounds, and there are two fundamental approaches that you can take, usually called &lt;em&gt;orchestration&lt;/em&gt; and &lt;em&gt;choreography&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Orchestration
&lt;/h3&gt;

&lt;p&gt;In an orchestrated saga, the saga is usually explicit, which means that there is a specific place in some codebase where the saga is written out in its entirety, and the "god service" that runs this code &lt;em&gt;orchestrates&lt;/em&gt; the entire business operation by calling out to all other services in the proper order to achieve the desired result. This makes it easy to reason about, but also feels like a step in the opposite direction from the decoupled, decentralized mindset that SOA/microservices traditionally embody. The god service must have direct knowledge of all the services it calls out to and how they respond, therefore becoming tightly coupled to them. Essentially, this approach amounts to normal RPC, just with an MQ sandwiched between the systems for better resilience, and possibly some other bells and whistles.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choreography
&lt;/h3&gt;

&lt;p&gt;In a choreographed saga, the saga is often (but not always) implicit—there's no single place in any codebase where the steps are laid out. Instead, you trigger the saga by emitting a particular message, which is handled by whatever handler represents the first step. This handler then emits its own messages, which triggers the next step, and so on. Subsequent steps are triggered in response to intercepting a particular message emitted by the previous step, and messages are fired and forgotten—handlers don't receive responses to the messages they emit. That's more in line with the decentralized mindset of microservices, but leads to incredibly messy systems that are difficult to reason about. There's actually a very deep reason for the inevitable messiness, and we'll talk about it at length in the final post of this series.&lt;/p&gt;

&lt;p&gt;Even if you ignore that, it still gets really tricky really fast. Sometimes, there's no reasonable business message to emit, because some part of the saga ends up not actually doing anything (e.g., we're handling &lt;code&gt;CreateCustomer&lt;/code&gt;, but the customer already exists and already contains that same data, so neither &lt;code&gt;CustomerCreated&lt;/code&gt; nor &lt;code&gt;CustomerChanged&lt;/code&gt; make sense). In that case, you probably want to continue with the next step anyway (since the &lt;em&gt;purpose&lt;/em&gt; of the step, i.e., the customer existing, was achieved), but there's no sensible message to emit, and so nothing to actually react to. This is commonly a problem in event-sourced applications, where the messages themselves &lt;em&gt;are&lt;/em&gt; the stored data, so you don't want to be emitting messages that don't mean anything. In other situations, you actually want to react to a combination of messages arriving (e.g., &lt;code&gt;CustomerCreated&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; &lt;code&gt;ContractCreated&lt;/code&gt;). Sometimes the order matters to you; sometimes it doesn't. Usually, the payloads matter (&lt;code&gt;ContractCreated&lt;/code&gt; needs &lt;code&gt;customer_id&lt;/code&gt; matching the &lt;code&gt;id&lt;/code&gt; in &lt;code&gt;CustomerCreated&lt;/code&gt;). Sometimes you want to wait for a particular amount of messages of a particular type (e.g., the appropriate number of &lt;code&gt;LineItemCreated&lt;/code&gt;) before continuing.&lt;/p&gt;

&lt;p&gt;Now, to be clear—I'm not saying these problems don't have solutions. They obviously do, otherwise these patterns couldn't be used. I'm just saying that there's significant baggage that needs to be dealt with, and dealing with it can feel like a game of whack-a-mole in terms of the tradeoffs one is forced to make, and involve significant effort to implement and maintain.&lt;/p&gt;

&lt;p&gt;But Scoop doesn't quite fit into either of these categories.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Rule of Structured Cooperation
&lt;/h2&gt;

&lt;p&gt;Sagas in Scoop are orchestrated in the sense that they are explicit—you will find a specific place in some codebase where a saga can be seen in its entirety. But they are also choreographed, in the sense that they do not directly call any services, and simply fire off one or more messages without processing responses to them.&lt;/p&gt;

&lt;p&gt;However, they do obey a rule that's somewhere halfway between orchestrated and choreographed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When they reach the end of a step in which messages were emitted, sagas suspend their execution and don't continue with the next step until all handlers of those messages have finished executing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This simple rule is at the heart of Scoop, at the heart of structured cooperation, and at the heart of everything these articles are about (5). And, as you'll see in the rest of the article, having sagas adhere to this rule has some pretty profound consequences.&lt;/p&gt;

&lt;p&gt;Let's take a look at some of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  No more race conditions
&lt;/h2&gt;

&lt;p&gt;In a system where structured cooperation is obeyed, it's impossible to trip over race conditions, such as those associated with eventual consistency, unless you deliberately go out of your way to inflict them upon yourself. That's because all handlers of all messages emitted in any previous steps are guaranteed to have finished successfully (and all handlers of any messages &lt;em&gt;they&lt;/em&gt; emitted, and so on). Crucially, &lt;em&gt;you don't need to do anything yourself&lt;/em&gt;—you don't need to check for side effects other services might have in order to determine if they've finished or not (6). If a step is executing, the previous steps and all their side effects have finished executing, period.&lt;/p&gt;

&lt;p&gt;Say you're not using structured cooperation, and, while importing some data, fire off a &lt;code&gt;CustomerCreation&lt;/code&gt; and &lt;code&gt;CustomerContractCreation&lt;/code&gt; message. Multiple services are listening to those messages and reacting to them. It could easily happen that a service starts reacting to &lt;code&gt;CustomerContractCreation&lt;/code&gt; before it finishes reacting to &lt;code&gt;CustomerCreation&lt;/code&gt;, which will lead to a &lt;code&gt;CustomerNotFound&lt;/code&gt; error. There are ways to deal with that, but it's a can of worms, e.g., how do you distinguish between a customer that hasn't been created &lt;em&gt;yet&lt;/em&gt; vs. an actual faulty message?&lt;/p&gt;

&lt;p&gt;If your system uses structured cooperation, that can never happen. &lt;code&gt;CustomerCreation&lt;/code&gt; is fired in a step preceding the one in which &lt;code&gt;CustomerContractCreation&lt;/code&gt; is, so you can guarantee that the &lt;em&gt;entire&lt;/em&gt; system is in a consistent state once you start executing any subsequent step.&lt;/p&gt;

&lt;p&gt;That guarantee is at the heart of what makes structured cooperation so powerful—it allows you to &lt;em&gt;reason&lt;/em&gt; about the state of the &lt;em&gt;entire&lt;/em&gt; system without actually writing any code that would require you to know anything about it. You don't need to care if there is one, zero, or 500 handlers listening to a message you emitted. You don't need to care if they themselves need to fire off 1000 messages of their own in order to react to your message, or run some dreadful local calculation that takes until October to complete. You fire off your messages and suspend, and Scoop will &lt;a href="https://www.youtube.com/watch?v=NU9JoFKlaZ0" rel="noopener noreferrer"&gt;wake you up when September ends&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Exceptions
&lt;/h2&gt;

&lt;p&gt;In a system where structured cooperation is obeyed, if I'm inside some saga that's handling some message, and that message is a "child" message of some other saga—i.e., it was emitted from within another saga—I'm guaranteed that the "parent" saga is still running, patiently waiting at the end of the step that emitted the message. Therefore, if my saga fails by throwing an (unhandled) exception, I have the option to propagate that exception to the parent, and rethrow it there. If the parent doesn't handle it, it bubbles up to &lt;em&gt;its&lt;/em&gt; parent, and so on—exactly in the same fashion as they would in regular code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distributed stack traces
&lt;/h3&gt;

&lt;p&gt;The first thing this allows you to do is to build something akin to a distributed stack trace—a description of the place an error happened, and how the "thread of execution" got there, but &lt;em&gt;across multiple services&lt;/em&gt;. This overlaps with the information you get from &lt;a href="https://microservices.io/patterns/observability/distributed-tracing.html" rel="noopener noreferrer"&gt;distributed tracing&lt;/a&gt;, except it doesn't require any separate technology or instrumentation—it's right there, inside your exception, where you need and expect it.&lt;/p&gt;

&lt;p&gt;An example is in order (notice we're naming the steps here—optional, but it makes the exceptions more informative):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Note: Each of the following could, in theory,&lt;/span&gt;
&lt;span class="c1"&gt;//       run in a completely different service,&lt;/span&gt;
&lt;span class="c1"&gt;//       and be written in a completely different&lt;/span&gt;
&lt;span class="c1"&gt;//       language!&lt;/span&gt;

&lt;span class="c1"&gt;// Pretend it listens to "parent-topic"&lt;/span&gt;
&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"parent-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"First parent step"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"child-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Second parent step"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This will not execute"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Pretend it listens to "child-topic"&lt;/span&gt;
&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"child-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"First child step"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Second child step"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"child-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Pretend it listens to "grandchild-topic"&lt;/span&gt;
&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"grandchild-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"First grandchild step"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Second grandchild step"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;MyException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My exception message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above, the log entries would appear in the expected &lt;code&gt;12345&lt;/code&gt; order (but, naturally, if each saga were running in adifferent service, the strings would get logged to different places).&lt;/p&gt;

&lt;p&gt;Additionally, in the service running &lt;code&gt;grandchild-handler&lt;/code&gt;, the following exception would be visible (for brevity, I'mtruncating the stack trace here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.blocking.coroutine.MyException"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"causes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grandchild-handler[0197dfd8-a424-7712-926f-b557d00203c0]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My exception message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stackTrace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DemoTest.kt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"className"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.blocking.coroutine.DemoTest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"functionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demoTest$lambda$8$lambda$7"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, in the service running &lt;code&gt;child-handler&lt;/code&gt;, the following exception would become visible (again truncating the&lt;br&gt;
stack trace):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.shared.coroutine.eventloop.ChildRolledBackException"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"causes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.blocking.coroutine.MyException"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"causes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grandchild-handler[0197dfd8-a424-7712-926f-b557d00203c0]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My exception message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"stackTrace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DemoTest.kt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"className"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.blocking.coroutine.DemoTest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"functionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demoTest$lambda$8$lambda$7"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"child-handler[0197dfd8-a424-73a9-926e-82e59ae4498a]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Child failure occurred while suspended in step [Second child step]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stackTrace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, in the service running &lt;code&gt;parent-handler&lt;/code&gt;, the following exception would become visible (truncating the&lt;br&gt;
stack trace again):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.shared.coroutine.eventloop.ChildRolledBackException"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"causes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.shared.coroutine.eventloop.ChildRolledBackException"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"causes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.blocking.coroutine.MyException"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"causes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grandchild-handler[0197dfd8-a424-7712-926f-b557d00203c0]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My exception message"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"stackTrace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"fileName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DemoTest.kt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"className"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"io.github.gabrielshanahan.scoop.blocking.coroutine.DemoTest"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"lineNumber"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
              &lt;/span&gt;&lt;span class="nl"&gt;"functionName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demoTest$lambda$8$lambda$7"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"child-handler[0197dfd8-a424-73a9-926e-82e59ae4498a]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Child failure occurred while suspended in step [Second child step]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"stackTrace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"parent-handler[0197dfd8-a3ec-7e97-9a99-1ca8a5f1598c]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Child failure occurred while suspended in step [First parent step]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stackTrace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those UUIDs next to the handler name are there because all sagas in Scoop are horizontally scalable by design, and the UUID identifies the actual instance of the saga that executed that particular step. We'll talk more about Scoop's execution model in the following article.&lt;/p&gt;

&lt;p&gt;You could even take the above a step further, store the stack trace at the point each message is actually emitted, and "concatenate" it with the stack trace of the child to get even more precise information. Scoop, being a POC, keeps things simple and doesn't do that, but it could.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distributed stack unwinding
&lt;/h3&gt;

&lt;p&gt;I mentioned earlier that sagas need compensating actions in order to revert the changes they made when an error causes them to fail. Since parents wait for their children to finish executing, Scoop can execute rollbacks in a very structured and predictable way, in effect achieving the equivalent of stack unwinding, but &lt;em&gt;across multiple services&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Compensating actions are defined as part of the step that they roll back and are executed in the opposite order to thesteps—subsequent steps are rolled back before preceding ones.&lt;/p&gt;

&lt;p&gt;Let's look at an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"parent-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"First parent step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"child-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"9"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Second parent step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This will never print"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"child-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"First child step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"8"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Second child step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grandchild-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"7"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"grandchild-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"First grandchild step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Second grandchild step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;MyException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My exception message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"""
                This will not execute, because the transaction
                hadn't committed yet when the exception was thrown,
                so a standard transaction rollback happened and there's
                nothing to compensate for.
                """&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the numbers to understand in what order things execute, but it's pretty intuitive—you're basically rolling back time.&lt;/p&gt;

&lt;p&gt;What about if there are multiple handlers listening to one topic, and only one of them fails, while the others succeed? Glad you asked!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"parent-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"First parent step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"child-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"7"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Second parent step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This will never print"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"child-handler-1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"First child-1 step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Second child-1 step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3a"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"grandchild-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"child-handler-2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"First child-2 step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2b"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4b"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Second child-2 step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3b"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;MyException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My exception message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"""
                This will not execute, because the transaction
                hadn't committed yet when the exception was thrown,
                so a standard transaction rollback happened and there's
                nothing to compensate for.
                """&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, keep in mind that each of those can potentially be running in a completely different service, written in a completely different language (assuming the same structured cooperation protocol is implemented in that language—more on that in the next article).&lt;/p&gt;

&lt;p&gt;The failing child handler first rolls itself back, after which control is transferred to the parent. The parent sees that one of its children has failed, so it triggers a rollback of the remaining children, waits for them to complete, then rolls back itself.&lt;/p&gt;

&lt;p&gt;Notice how I logged some of the numbers with a letter—that's to represent that these blocks are running in parallel, so you can't guarantee their relative order. You could get any of &lt;code&gt;2a-2b-3a-3b-4b&lt;/code&gt;, &lt;code&gt;2a-3a-2b-3b-4b&lt;/code&gt;, &lt;code&gt;2a-2b-3b-4b-3a&lt;/code&gt;, or any other combination where each &lt;code&gt;2x&lt;/code&gt; comes before &lt;code&gt;3x&lt;/code&gt; and &lt;code&gt;3b&lt;/code&gt; comes before &lt;code&gt;4b&lt;/code&gt;. The rest of the logs will be ordered deterministically.&lt;/p&gt;

&lt;p&gt;One last example: if any messages were emitted during any step that is being rolled back, compensating actions of those "child" handlers are run first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"parent-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"First parent step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"child-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"7"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Second parent step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"4"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;MyException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My exception message"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;"""
                This will not execute, because the transaction
                hadn't committed yet when the exception was thrown,
                so a standard transaction rollback happened and there's
                nothing to compensate for.
                """&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"child-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"First child step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"6"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s"&gt;"Second child step"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are other cases I'm not discussing here—what if there are more than two child handlers? What if more than one handler fails? What if a rollback step fails? I'll discuss some of these in the next article; others, I'll leave up to the motivated reader to look up in tests. For now, suffice it to say that in all those scenarios, Scoop is well-behaved, and you can probably figure out what that behavior is just by thinking about what it &lt;em&gt;should be&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;As a consequence of this approach to handling failures, you get a lot of non-trivial features for free or very little work, such as cancellations, timeouts, rollbacks triggered by a user action, and more. Scoop supports all of these, and we'll talk about some of them in the next article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resource handling
&lt;/h2&gt;

&lt;p&gt;Another key feature recovered by adhering to structured cooperation is &lt;em&gt;resource handling&lt;/em&gt;. By that, I mean the distributed analogue to various language constructs that allow you to delimit a block of code within which a resource is available, while also ensuring that resource is cleaned up regardless of how that block is exited (normally or exceptionally). This is typically done via &lt;code&gt;try-finally&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A resource is anything that's considered expensive. In the context of distributed systems, think less "opening a file" and more "spinning up a cluster of 100 servers to run a calculation".&lt;/p&gt;

&lt;p&gt;In Scoop, because of the way failures are guaranteed to propagate, this is easy to do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;
&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"root-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;tryFinallyStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;k8Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spinUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"do-intensive-calculation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;k8Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spinDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"123"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's important is that &lt;code&gt;tryFinallyStep&lt;/code&gt; isn't some special primitive—you can build it yourself using what we've already introduced, plus a single additional thing, &lt;code&gt;CooperationContext&lt;/code&gt;, which we'll talk about in the next article.&lt;/p&gt;

&lt;p&gt;Alternatively, you could wrap the &lt;code&gt;k8Service&lt;/code&gt; in a saga of its own, and take advantage of the way Scoop works natively.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Listens on "k8-spinup" topic&lt;/span&gt;
&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"k8-spinup"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;invoke&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;k8Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spinUp&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;rollback&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;throwable&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="n"&gt;k8Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spinDown&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Listens on "k8-spindown" topic&lt;/span&gt;
&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"k8-spindown"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;k8Service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;spinDown&lt;/span&gt;&lt;span class="p"&gt;(&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;extract&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"root-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"k8-spinup"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"request-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"num"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"do-intensive-calculation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k8-spindown&lt;/span&gt;&lt;span class="s"&gt;", JsonObject().put("&lt;/span&gt;&lt;span class="n"&gt;request-id&lt;/span&gt;&lt;span class="s"&gt;", 123))
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If rolling back whatever &lt;code&gt;do-intensive-calculation&lt;/code&gt; entailed was itself also intensive, you could even consider having &lt;code&gt;k8Service.spinUp&lt;/code&gt; as a compensating action for the &lt;code&gt;k8-spindown&lt;/code&gt; saga step. The sky's the limit here.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if I don't want to cooperate?
&lt;/h2&gt;

&lt;p&gt;The fundamental way structured cooperation works is by &lt;em&gt;synchronizing&lt;/em&gt; parts of a distributed system—in essence, structured cooperation is a synchronization primitive, much like structured concurrency is. It allows you to make explicit things that &lt;em&gt;depend&lt;/em&gt; on each other, by allowing you to &lt;em&gt;order&lt;/em&gt; them so that &lt;em&gt;that which depends&lt;/em&gt; only starts executing after &lt;em&gt;that which is depended on&lt;/em&gt; has finished executing. Components of the distributed system &lt;em&gt;cooperate&lt;/em&gt; to ensure this is always the case, waiting for each other if needed.&lt;/p&gt;

&lt;p&gt;Naturally, there are times where you &lt;em&gt;don't&lt;/em&gt; want this behavior—where you want to fire off a message that's &lt;em&gt;independent&lt;/em&gt; of the operation you're implementing.&lt;/p&gt;

&lt;p&gt;This is how that's done in Scoop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nf"&gt;saga&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"root-handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;step&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchOnGlobalScope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"some-topic"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;JsonObject&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above, you're explicitly saying that you're launching a completely independent hierarchy of messages. You're not waiting for it to complete. If it fails, you won't be notified about it. You &lt;em&gt;can't&lt;/em&gt;, because you're not waiting—there's nobody to notify. If your saga rolls back, that message hierarchy will not be notified. It can't be, because who knows what state it's in—it might not have been completed yet, or it might have already been rolled back, or it might be in the process of rolling back, or something else.&lt;/p&gt;

&lt;p&gt;I want to emphasize that this is not a fringe feature. Structured cooperation is &lt;em&gt;tool&lt;/em&gt;, not a dogma—you should &lt;strong&gt;only use it when you need to solve the problem it was designed to solve&lt;/strong&gt;. If the operations performed by two different services &lt;em&gt;depend&lt;/em&gt; on each other, then that dependency is there no matter what you do—you just &lt;em&gt;can't&lt;/em&gt; start B before A finishes, period, and structured cooperation is an excelent tool to make that dependency explicit, and provide the necessary synchronization.&lt;/p&gt;

&lt;p&gt;But if the operations performed by two operations are &lt;em&gt;independent&lt;/em&gt;, then you have the option of choosing. Do you want to have predictable execution, distributed exceptions, stack traces and just general peace of mind, at the cost of additional overhead and latency? Great—keep using structured cooperation. But if performance is an issue, you always have the option of falling back to doing things the old way by launching an independent message hierarchy.&lt;/p&gt;

&lt;p&gt;In Scoop, the manner in which you decouple execution hierarchies has additional advantages:&lt;/p&gt;

&lt;p&gt;1) you're being explicit—the &lt;code&gt;launchOnGlobalScope&lt;/code&gt; is immediately visible, can be searched for, etc.,&lt;/p&gt;

&lt;p&gt;2) whatever &lt;code&gt;some-topic&lt;/code&gt; handlers may be launched, &lt;em&gt;they&lt;/em&gt; can still participate in structured cooperation amongst&lt;br&gt;
themselves.&lt;/p&gt;

&lt;p&gt;So in effect, you get the best of both worlds—the ability to synchronize the parts of a distributed system that &lt;em&gt;need&lt;/em&gt; to be synchronized, while also not needlessly slowing down the parts that &lt;em&gt;must not be&lt;/em&gt;. Sometimes, the stars align, and you get to have your cake and eat it too.&lt;/p&gt;

&lt;p&gt;Incidentally, basically the same thing happens when only part of the (distributed) system implements structured cooperation, and another part doesn't. The part that doesn't is just independent of the part that does, and you have no guarantees about anything that happens there, but it doesn't stop you from using structured cooperation in some subset of your system. As a consequence, you can switch to using structured cooperation &lt;em&gt;gradually&lt;/em&gt;, service by service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I hope I've started to convince you that structured cooperation will make your interactions with distributed systems dramatically simpler, or at least piqued your curiosity. In the &lt;a href="https://dev.to/posts/implementing-structured-cooperation"&gt;next article&lt;/a&gt;, we'll take a closer look at how structured cooperation is implemented in Scoop.&lt;/p&gt;




&lt;p&gt;(1) &lt;em&gt;Scoop has nothing to do with event sourcing. I'm including the comparison solely because Axon, by design, is built for distributed environments, and forces you to model things accordingly.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(2) &lt;em&gt;At times, you might notice the word &lt;em&gt;coroutine&lt;/em&gt; being used, e.g., in package names. That isn't a reference to &lt;a href="https://kotlinlang.org/docs/coroutines-overview.html" rel="noopener noreferrer"&gt;Kotlin coroutines&lt;/a&gt;, but rather to Scoop's own (distributed) implementation. Kotlin coroutines are not used anywhere in Scoop.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(3) &lt;em&gt;Since Scoop uses its own MQ on top of Postgres, publishing messages only when the transaction commits is easy to do---the messages are part of the transaction. If it were implemented in a realistic context, an external MQ would likely be involved, which means this would need to use something like the &lt;a href="https://microservices.io/patterns/data/transactional-outbox.html" rel="noopener noreferrer"&gt;outbox pattern&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(4) &lt;em&gt;You might be wondering how you share data between steps, if you have no guarantee they will all be run by the same instance of a service. This is what &lt;code&gt;CooperationContext&lt;/code&gt; is for, and we'll talk about it in the next article. Basically, it's the equivalent of &lt;a href="https://projectreactor.io/docs/core/release/reference/advancedFeatures/context.html" rel="noopener noreferrer"&gt;reactive context&lt;/a&gt;, &lt;a href="https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.coroutines/-coroutine-context/" rel="noopener noreferrer"&gt;CoroutineContext&lt;/a&gt;, etc.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(5) &lt;em&gt;If you’re getting structured concurrency vibes, you’re exactly right! That’s where structured cooperation gets its name from.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;(6) &lt;em&gt;Wait—how does Scoop find out &lt;em&gt;which&lt;/em&gt; handlers it was supposed to have been waiting for in the first place? That's a very important question and not trivial to answer in the context of distributed systems. The way you decide to answer it—because it &lt;em&gt;will&lt;/em&gt; be up to you—is one of the key decisions you need to make when using Scoop, depends on how your system is architected, has implications related to the &lt;a href="https://en.wikipedia.org/wiki/CAP_theorem" rel="noopener noreferrer"&gt;CAP theorem&lt;/a&gt;, and is what that second required parameter to &lt;code&gt;saga&lt;/code&gt; I mentioned earlier—an instance of &lt;code&gt;EventLoopStrategy&lt;/code&gt;—is there for. We'll discuss this whole topic at length in the following article.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>distributedsystems</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Learning Lisp - making sense of xrefs in SLIME</title>
      <dc:creator>Gabriel Shanahan</dc:creator>
      <pubDate>Sun, 11 Aug 2024 16:04:14 +0000</pubDate>
      <link>https://dev.to/gabrielshanahan/learning-lisp-making-sense-of-xrefs-in-slime-2b6g</link>
      <guid>https://dev.to/gabrielshanahan/learning-lisp-making-sense-of-xrefs-in-slime-2b6g</guid>
      <description>&lt;p&gt;This is a post in an ongoing series I decided to write on my journey learning Lisp. Read the &lt;a href="https://dev.to/gabrielshanahan/learning-lisp-56f4"&gt;brief intro&lt;/a&gt; to understand if this is for you.&lt;/p&gt;

&lt;p&gt;The following was written by a beginner, with predictable consequences for how correct the contents may actually be.&lt;/p&gt;




&lt;p&gt;After setting up a &lt;em&gt;very&lt;/em&gt; basic and rudimentary Emacs/SLIME environment (post on that coming up soon), one of the first things I started testing out was my ability to navigate code - specifically, the &lt;a href="https://www.jetbrains.com/help/idea/navigating-through-the-source-code.html#go_to_declaration" rel="noopener noreferrer"&gt;Go to declaration&lt;/a&gt; functionality I use every 5 seconds of my day. There is little that's more important to my ability to learn about code.&lt;/p&gt;

&lt;h2&gt;
  
  
  xref &amp;amp; TAGS
&lt;/h2&gt;

&lt;p&gt;Emacs has the builtin &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Xref.html" rel="noopener noreferrer"&gt;xref&lt;/a&gt; facility, short for "cross-references", which deals with, well, cross-referencing - things like "find definition", "find references", etc. However, from what I could tell, this seemed to require running one of several &lt;a href="https://www.reddit.com/r/emacs/comments/iusam9/tags_etags_ctags_gtags/" rel="noopener noreferrer"&gt;tools&lt;/a&gt; on the codebase I was interested in, which generates a &lt;code&gt;TAGS&lt;/code&gt; file that contains metadata necessary for such navigation to work. This was very different from what I'm used to from "mainstream" development, where these things are usually done internally by the IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  First attempts
&lt;/h2&gt;

&lt;p&gt;Given that, I was expecting this kind of navigation to not work out of the box. In other words, after I set SLIME up, typed e.g. &lt;code&gt;(with-open-file&lt;/code&gt; in the REPL, and pressed &lt;code&gt;M-.&lt;/code&gt;, the mapping for &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Looking-Up-Identifiers.html" rel="noopener noreferrer"&gt;&lt;code&gt;xref-find-definitions&lt;/code&gt;&lt;/a&gt;, (or so I thought), nothing would happen.&lt;/p&gt;

&lt;p&gt;I was wrong - instead, a new buffer opened at the definition of the &lt;code&gt;with-open-file&lt;/code&gt; macro, inside the &lt;a href="https://sbcl.org/" rel="noopener noreferrer"&gt;SBCL&lt;/a&gt; source code, which is the Lisp implementation I'm using.&lt;/p&gt;

&lt;p&gt;This surprised me, so I proceeded to try the reverse, the equivalent of &lt;a href="https://www.jetbrains.com/help/idea/find-highlight-usages.html#usages_in_file" rel="noopener noreferrer"&gt;Find uses&lt;/a&gt; in IDEA. A quick Google search told me that the SLIME keybinding for that was &lt;code&gt;C-c C-w c&lt;/code&gt; (notice how I'm conflating SLIME with xrefs - this was part of my mistake, but I didn't know any better at this point), but running that on the same symbol resulted in "No references found". &lt;a href="https://www.reddit.com/r/lisp/comments/1emiwcf/trying_to_figure_crossreferencing_in_emacs_with" rel="noopener noreferrer"&gt;It was pointed out to me&lt;/a&gt; that this keybinding was in fact only effective for functions, while &lt;code&gt;with-open-file&lt;/code&gt; is a macro, but even switching to &lt;code&gt;C-c C-w m&lt;/code&gt; didn't help.&lt;/p&gt;

&lt;p&gt;This was strange, and after being encouraged to do so, I went digging to find out why.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mistake
&lt;/h2&gt;

&lt;p&gt;First and foremost, credit where credit's due - &lt;a href="https://stackoverflow.com/a/69675965" rel="noopener noreferrer"&gt;this SO answer&lt;/a&gt; helped me immeasurably.&lt;/p&gt;

&lt;p&gt;It turns out that my conception of what was going on was incorrect form the start, which was the source of a large part of my confusion. When SLIME is active, &lt;code&gt;M-.&lt;/code&gt; is not, in fact, bound to &lt;code&gt;xref-find-definitions&lt;/code&gt;, but to &lt;a href="https://slime.common-lisp.dev/doc/html/Finding-definitions.html" rel="noopener noreferrer"&gt;&lt;code&gt;slime-edit-definition&lt;/code&gt;&lt;/a&gt; instead. What this means is that SLIME hijacks &lt;code&gt;M-.&lt;/code&gt; to completely remove the native &lt;code&gt;xrefs&lt;/code&gt; facilities from the picture, and does its own thing instead.&lt;/p&gt;

&lt;p&gt;To understand what exactly it does, you need to understand what &lt;a href="https://slime.common-lisp.dev/" rel="noopener noreferrer"&gt;&lt;code&gt;SLIME&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.cliki.net/SWANK" rel="noopener noreferrer"&gt;&lt;code&gt;Swank&lt;/code&gt;&lt;/a&gt; actually are.&lt;/p&gt;

&lt;h2&gt;
  
  
  SLIME, Swank &amp;amp; Jupyter Notebooks
&lt;/h2&gt;

&lt;p&gt;A useful (though likely imprecise) way to understand what SLIME and Swank are is the think of &lt;a href="https://jupyter.org/" rel="noopener noreferrer"&gt;Jupyter notebooks&lt;/a&gt;. In them, you have two fundamental parts - the "frontend", which is the actual notebook and the part you interact with, and the "backend" (&lt;a href="https://docs.jupyter.org/en/latest/projects/kernels.html" rel="noopener noreferrer"&gt;kernel&lt;/a&gt; in Jupyter parlance) which is a process to which the commands from the notebook get sent so they can be evaluated, and results are sent back. Each kernel represents a language (Python being the default, but a &lt;a href="https://github.com/jupyter/jupyter/wiki/Jupyter-kernels" rel="noopener noreferrer"&gt;large number of other kernels&lt;/a&gt; are also available).&lt;/p&gt;

&lt;p&gt;In this metaphor, SLIME is the Jupyter notebook, and Swank is the kernel. A different comparison might be to an editor and an &lt;a href="https://en.wikipedia.org/wiki/Language_Server_Protocol" rel="noopener noreferrer"&gt;LSP server&lt;/a&gt;. However, I prefer the Jupyter analogy, since a large part of what you can do via Swank is actually to &lt;a href="https://stackoverflow.com/questions/46499463/hot-debug-and-swap-in-common-lisp" rel="noopener noreferrer"&gt;interact with the running Lisp image&lt;/a&gt;, evaluating code, thereby affecting the process as its running - something that those of us coming from more mainstream languages find completely unimaginable.&lt;/p&gt;

&lt;p&gt;As such, when you browse the code of &lt;code&gt;SLIME&lt;/code&gt;, you will quickly find that &lt;a href="https://github.com/slime/slime/blob/3fca003ac639df989af3cfbfc8112bd34ea1a042/slime.el#L3721" rel="noopener noreferrer"&gt;&lt;code&gt;slime-edit-definition&lt;/code&gt;&lt;/a&gt; delegates to &lt;code&gt;swank:find-definitions-for-emacs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Swank backends
&lt;/h2&gt;

&lt;p&gt;The hunt continues in &lt;a href="https://github.com/slime/slime/blob/3fca003ac639df989af3cfbfc8112bd34ea1a042/swank.lisp#L3022" rel="noopener noreferrer"&gt;&lt;code&gt;swank.el&lt;/code&gt;&lt;/a&gt;, where the essential part is the call to &lt;code&gt;find-definitions&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This brings us to &lt;a href="https://github.com/slime/slime/blob/3fca003ac639df989af3cfbfc8112bd34ea1a042/swank/backend.lisp#L1036" rel="noopener noreferrer"&gt;&lt;code&gt;swank/backend.lisp&lt;/code&gt;&lt;/a&gt;, from which we can gleam that &lt;code&gt;find-definitions&lt;/code&gt; is actually implemented on a per-lisp-implementation basis - different implementations provide different ways to access the information that's needed (&lt;a href="https://github.com/slime/slime/blob/3fca003ac639df989af3cfbfc8112bd34ea1a042/swank/scl.lisp#L278" rel="noopener noreferrer"&gt;some&lt;/a&gt; don't even provide the implementation at all!). Swank abstracts over these specifics and exposes a unified API for SLIME to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;At this point, we've already learned a lot - namely, that it's the &lt;em&gt;implementation&lt;/em&gt; (i.e. the running lisp process) that's responsible for providing the source locations, and anything else we might be interested in. Clearly, the only way it can learn that information is when we're actually compiling a file, and that's exactly how it works.&lt;/p&gt;

&lt;p&gt;In the case of SBCL, it uses &lt;a href="https://github.com/sbcl/sbcl/blob/39b416bab2d215e7e86b724d181d1eaf531e1759/contrib/sb-introspect/introspect.lisp#L196" rel="noopener noreferrer"&gt;&lt;code&gt;sb-introspect:find-definition-sources-by-name&lt;/code&gt;&lt;/a&gt;, which seems to use a in-memory database represented by &lt;a href="https://github.com/sbcl/sbcl/blob/39b416bab2d215e7e86b724d181d1eaf531e1759/src/compiler/globaldb.lisp#L155" rel="noopener noreferrer"&gt;&lt;code&gt;info&lt;/code&gt;&lt;/a&gt;. This is &lt;a href="https://github.com/sbcl/sbcl/blob/39b416bab2d215e7e86b724d181d1eaf531e1759/src/compiler/main.lisp#L696" rel="noopener noreferrer"&gt;updated during the compilation process&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;One final question remains - why don't some of the operations, like "Show callers", work on internal SBCL symbols? The answer is that, by default, SBCL is built without the &lt;code&gt;:sb-xref-for-internals&lt;/code&gt; flag, which means that it deliberately doesn't store the necessary data for internal functions, thereby saving &lt;a href="https://github.com/sbcl/sbcl/blob/39b416bab2d215e7e86b724d181d1eaf531e1759/src/cold/base-target-features.lisp-expr#L224" rel="noopener noreferrer"&gt;5-6MB&lt;/a&gt; of space. Calls to functions that need this data, therefore, come up empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;p&gt;Apart from having a much better understanding of how things work under the hood, I confess there's another part of this that I'm nothing short of astounded by.&lt;/p&gt;

&lt;p&gt;I have somewhere around two weeks of spending my evenings reading about Lisp under my belt. I've written 0 lines of code, I'm at chapter 9 of &lt;a href="https://gigamonkeys.com/book/" rel="noopener noreferrer"&gt;Peter Seibel's Practical Common Lisp&lt;/a&gt;. I have as close to 0 experience about anything as you could possibly have, yet I was able to answer a question about the inner workings of something that spans three different pieces of software in about two hours of clicking through GitHub. I didn't even clone the repo's, cause I started this hunt out thinking that cross-referencing didn't work reliably in my setup.&lt;/p&gt;

&lt;p&gt;I find it completely un-friggin-imaginable that I would even attempt something similar in another language. Just imagining that I would want to answer a question that would require me to traverse the source code of the Java compiler, IDEA and the Java LSP Server makes me instantly want to kill myself. And in that case, at least I would have some vague mental image of what was what - at the outset, I know at least a little bit of what the Java compiler/IDEA/LSP are, what their capabilities are, etc. But here, I had absolutely no clue about anything going in, actually had a few misconceptions that were throwing me off the scent, no tooling, no experience, no nothing.&lt;/p&gt;

&lt;p&gt;That software of such complexity can be reduced to something that a complete beginner can traverse and find answers in in a matter of hours speaks volumes to why I want to learn this language in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When using SLIME, the standard xref facilities of emacs are completely bypassed - no TAGS involved&lt;/li&gt;
&lt;li&gt;SLIME communicates with Swank, which provides a unified API for the functionalities it needs, such as finding definitions. This is implemented in an implementation-specific way for each Lisp implementation. The relationship between SLIME and Swank can be thought of as the relationship between Jupyter and its kernels&lt;/li&gt;
&lt;li&gt;Most Lisp implementations track source code information and relationships in some way, and provide implementation-specific functions to expose this information. These are then used in the swank adapters.&lt;/li&gt;
&lt;li&gt;Specifically SBCL doesn't collect this data for its own internal functions by default - this can be changed by building it with &lt;code&gt;:sb-xref-for-internals&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;A previous version incorrectly listed the author of Practical Common Lisp as Paul Graham. Thanks to &lt;a class="mentioned-user" href="https://dev.to/vindarel"&gt;@vindarel&lt;/a&gt; for pointing this out!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>learninglisp</category>
      <category>lisp</category>
      <category>slime</category>
      <category>xrefs</category>
    </item>
    <item>
      <title>Learning Lisp</title>
      <dc:creator>Gabriel Shanahan</dc:creator>
      <pubDate>Sun, 11 Aug 2024 12:42:10 +0000</pubDate>
      <link>https://dev.to/gabrielshanahan/learning-lisp-56f4</link>
      <guid>https://dev.to/gabrielshanahan/learning-lisp-56f4</guid>
      <description>&lt;p&gt;One of the biggest obstacles for a beginner is that learning Lisp doesn't actually mean just learning Lisp, but also learning to think in a very different way, and working with a completely unfamiliar toolset - emacs being probably the most prominent member of the latter category.&lt;/p&gt;

&lt;p&gt;Since most resources tend to focus on only one of these problems, I decided to blog about my journey learning everything at once, in the hopes that the obstacles I encounter and overcome might help others as well. I'll be tagging all posts with the tag &lt;code&gt;#learninglisp&lt;/code&gt;, so they're easy to find.&lt;/p&gt;

&lt;p&gt;I am targeting this at an audience of programmers who have experience working in "mainstream" languages on projects of non-trivial sizes - if you're the "Java/JavaScript/Python/PHP/... developer with 3-5 years of experience" everyone seems to be looking for, this is for you. However, I won't be assuming knowledge of any particular language or language feature, only general knowledge. In short, if the terms 'class', 'version control', 'thread', 'unit testing', 'package manager', 'OTEL', 'SQL' etc. are things you encounter every day, you're part of my target audience.&lt;/p&gt;

&lt;p&gt;Let's go!&lt;/p&gt;

</description>
      <category>learninglisp</category>
      <category>lisp</category>
      <category>emacs</category>
    </item>
  </channel>
</rss>
