<?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: Amit Kamble</title>
    <description>The latest articles on DEV Community by Amit Kamble (@amitjkamble).</description>
    <link>https://dev.to/amitjkamble</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%2F3834625%2Feb5979aa-cb57-487e-b190-d44c2514aced.png</url>
      <title>DEV Community: Amit Kamble</title>
      <link>https://dev.to/amitjkamble</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/amitjkamble"/>
    <language>en</language>
    <item>
      <title>How to scale Kafka maintaining Order of events</title>
      <dc:creator>Amit Kamble</dc:creator>
      <pubDate>Tue, 24 Mar 2026 08:21:51 +0000</pubDate>
      <link>https://dev.to/amitjkamble/kafka-ordering-in-the-real-world-how-to-scale-without-killing-performance-37fo</link>
      <guid>https://dev.to/amitjkamble/kafka-ordering-in-the-real-world-how-to-scale-without-killing-performance-37fo</guid>
      <description>&lt;h2&gt;
  
  
  Intro: The Sequential Processing Paradox
&lt;/h2&gt;

&lt;p&gt;Some of the business processes requires events to be processed in order e.g. In e-commerce, the order of events is important. You cannot process &lt;code&gt;OrderFulfilled&lt;/code&gt; before &lt;code&gt;OrderCreated&lt;/code&gt; or &lt;code&gt;PaymentAuthorized&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If we have single partition, Kafka does ensure chronological order. However, this significantly affects the performance, since we cannot configure multiple consumers to do processing in parallel. On the other hand, if we have multiple partitions, then we would lose the processing order, since Kafka does not guarantee order across partitions. &lt;/p&gt;

&lt;p&gt;Standard Kafka advice is to use multiple partitions and use a &lt;strong&gt;Message Key&lt;/strong&gt; (like &lt;code&gt;order_id&lt;/code&gt;) to ensure all related events land in the same partition in chronological order. With this approach we can ensure that events related to same key (like &lt;code&gt;order_id&lt;/code&gt;) are processed in order.&lt;/p&gt;

&lt;p&gt;However, if we use this approach, we still need to follow some best practices to make this solution robust. Also, quite often we use the Transactional Outbox (For Data Consistency) and Inbox pattern (Idempotent processing) along with Kafka, we need to make sure that Outbox and Inbox retain the order.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Concepts
&lt;/h2&gt;

&lt;p&gt;To maintain order at scale, we must move beyond a simple "Kafka-only" view to a three-stage architectural safety chain.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Transactional Outbox
&lt;/h3&gt;

&lt;p&gt;The Outbox pattern works by saving your business data (the Order) and the event message (the Intent) into the same database in a single, atomic step. A separate "Relay" process then polls this table and publishes the messages to Kafka, ensuring that if your database update succeeds, your message eventually follows.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it Can Mess Up the Order&lt;/strong&gt;&lt;br&gt;
If your Relay process picks up messages and tries to send them in parallel without a strategy, OrderUpdated might accidentally reach Kafka before OrderCreated. Similarly, if a message fails and you move to the next one before retrying the first, you’ve broken the chronological chain. Without using a Message Key (like order_id) during this relay step, Kafka cannot guarantee that related events stay in the same "line" for the consumer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What to Ensure for Success&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sequential Polling&lt;/strong&gt;: Ensure your Relay reads from the Outbox table using a strict ORDER BY (usually a sequence ID or timestamp) so events are sent exactly as they occurred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic Partitioning&lt;/strong&gt;: Always attach a consistent Business Key to every message so Kafka routes them to the same partition every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Max In-Flight Requests&lt;/strong&gt;: Limit the producer to only one "unconfirmed" request at a time (or use Kafka’s idempotent producer settings) to prevent a network retry from flipping the order of two messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clean-up Logic&lt;/strong&gt;: Implement a "Janitor" task to delete processed rows from the Outbox table; a bloated table will slow down your Relay and eventually delay your entire event stream.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Keyed Partitioning
&lt;/h3&gt;

&lt;p&gt;By using &lt;code&gt;order_id&lt;/code&gt; as the Kafka Key, we ensure the "Log" for that specific order is physically sequential. Kafka handles the heavy lifting of keeping &lt;code&gt;v1&lt;/code&gt; ahead of &lt;code&gt;v2&lt;/code&gt; within the partition.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. The Sub-Inbox
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Why we use an Inbox first&lt;/strong&gt;&lt;br&gt;
The Inbox acts as a "buffer" between Kafka and your business logic. By saving the message to a database table immediately and acknowledging Kafka, you ensure the message is never lost if your service crashes, and you keep the Kafka partition moving at high speed without waiting for slow external APIs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How a "Sub-Inbox" differs&lt;/strong&gt;&lt;br&gt;
In a standard Inbox, you often only need a simple status and received_at timestamp. In a Sub-Inbox, the table must be "Aware" of the specific business entity it is processing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, in Standard Inbox, you have one worker (or a very small number) that pulls the oldest message, processes it, and moves to the next. If the first message is slow, the worker waits, and the whole table sits idle. In Sub-Inbox you have a pool of many workers. The "Order" is preserved because we use database locking as the gatekeeper. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;How the Worker Pool Operates&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Imagine a team of 10 workers standing in front of a giant digital task board (the Inbox Table). Instead of the first worker taking the top 10 tasks, each worker acts independently but follows a shared logic:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Selective Search:&lt;/strong&gt; A worker looks at the table and asks: "Give me the oldest pending message, but ignore any message whose order_id is already being processed by another worker."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Instant Lock:&lt;/strong&gt; Once the worker finds an available order_id (e.g., Order-101), it immediately places a "Lock" on that ID in the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Parallel Leap:&lt;/strong&gt; While Worker 1 is busy with Order-101, Worker 2 looks at the board. It sees Order-101 is locked, so it instantly "skips" over it and grabs Order-102.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Release:&lt;/strong&gt; Only when Worker 1 completely finishes the task and deletes (or marks "Complete") the row for Order-101 is that ID "unlocked." Now, if there is a second message for Order-101 (like a shipping update), the next available worker can safely grab it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Things to Ensure&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The "Skip Locked" Command:&lt;/strong&gt; In your code, you must use a specific database command (like SELECT ... FOR UPDATE SKIP LOCKED). Without the "SKIP LOCKED" part, Worker 2 would sit and wait for Worker 1 to finish, which defeats the whole purpose of having a pool.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transaction Boundaries:&lt;/strong&gt; The "Lock" must live inside a database transaction. The worker should: Start Transaction → Lock &amp;amp; Get Task → Process Task → Update Status → Commit Transaction. If anything fails in the middle, the transaction rolls back and the lock disappears, keeping the data safe.&lt;/p&gt;


&lt;h2&gt;
  
  
  Important Decisions &amp;amp; Future-Proofing
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1. Partition Strategy: Planning for Growth
&lt;/h3&gt;

&lt;p&gt;Partitions are your unit of parallelism. You cannot easily increase partition counts later without changing the hash result of your keys, which breaks the sequence.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Guidance:&lt;/strong&gt; Over-partition from Day 1. If you need 10 now, create 60. This allows you to scale your consumer group up to 60 instances without a complex data migration.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  2. Decision Matrix: Choosing Your Strategy
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Raw Kafka Key&lt;/th&gt;
&lt;th&gt;Standard Inbox&lt;/th&gt;
&lt;th&gt;Sub-Inbox (Parallel)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ordering Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per Partition&lt;/td&gt;
&lt;td&gt;Per Key&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Per Key&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 Thread/Partition&lt;/td&gt;
&lt;td&gt;1 Thread/Service&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;N Threads/Service&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure Impact&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Blocks Partition&lt;/td&gt;
&lt;td&gt;Blocks Service&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Blocks ONE Order Only&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Technical Guide: Spring Boot Implementation
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Producer: Transactional Outbox
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderDTO&lt;/span&gt; &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Persist Business State&lt;/span&gt;
    &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Persist Event to Outbox in the same transaction&lt;/span&gt;
    &lt;span class="n"&gt;outboxRepo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OutboxEvent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; 
        &lt;span class="s"&gt;"ORDER_CREATED"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Consumer: Inbox + Manual Ack
&lt;/h3&gt;

&lt;p&gt;Set ack-mode: manual_immediate in your application.yml.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@KafkaListener&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"orders"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;groupId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"fulfillment-service"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onMessage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ConsumerRecord&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Acknowledgment&lt;/span&gt; &lt;span class="n"&gt;ack&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Unique constraint on (order_id + version) handles idempotency&lt;/span&gt;
    &lt;span class="n"&gt;inboxRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;save&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InboxEntry&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; 
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; 
        &lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Acknowledge ONLY after the DB transaction commits&lt;/span&gt;
    &lt;span class="n"&gt;ack&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;acknowledge&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; 
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retries and the "Problem Bin" (DLQ)
&lt;/h3&gt;

&lt;p&gt;In an ordered flow, moving a failed PaymentAuthorized message to a DLQ while letting ShipmentRequested proceed results in corrupted state.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blocking Retries: Use Exponential Backoff. Because of the Sub-Inbox, a retry for Order-101 only blocks Order-101.&lt;/li&gt;
&lt;li&gt;The Order Lock: If a message reaches the retry limit and moves to a DLQ, you must flag that order_id as "Blocked" in the database. No subsequent events for that ID should be processed until the DLQ item is resolved by a human.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Specifying and Testing the Contract
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Specification: Use a Schema Registry. Define order_id as a required partitioning key.&lt;/li&gt;
&lt;li&gt;Verification: Use Pact (Contract Testing). The Consumer defines a "Pact" (e.g., "I require a non-null order_id"), and the Producer verifies this in their CI/CD pipeline.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Production Checklist
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Partitions: Created with enough headroom for 3 years of growth.&lt;/li&gt;
&lt;li&gt;Database Index: INBOX table has a composite index on (status, order_id).&lt;/li&gt;
&lt;li&gt;Idempotency: Unique constraints prevent duplicate processing on retries.&lt;/li&gt;
&lt;li&gt;Monitoring: Alerting set for "Inbox Age" (time an event waits in the DB).&lt;/li&gt;
&lt;li&gt;Cleanup: A background job (TTL) purges processed rows every 24 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Takeaway
&lt;/h3&gt;

&lt;p&gt;Strict ordering is often viewed as a performance tax. By shifting the logic from the rigid Kafka partition to a Database-backed Sub-Inbox, you get the Sequence Integrity along with parallel processing&lt;/p&gt;

</description>
      <category>kafka</category>
      <category>partition</category>
      <category>performance</category>
      <category>robust</category>
    </item>
    <item>
      <title>Mastering the Saga Pattern: Achieving Data Consistency in Microservices</title>
      <dc:creator>Amit Kamble</dc:creator>
      <pubDate>Fri, 20 Mar 2026 07:43:08 +0000</pubDate>
      <link>https://dev.to/amitjkamble/mastering-the-saga-pattern-achieving-data-consistency-in-microservices-52c1</link>
      <guid>https://dev.to/amitjkamble/mastering-the-saga-pattern-achieving-data-consistency-in-microservices-52c1</guid>
      <description>&lt;p&gt;A &lt;em&gt;transaction&lt;/em&gt; represents a unit of work, which can include multiple&lt;br&gt;
operations. In a monolith, placing an order is easy: you wrap your&lt;br&gt;
database calls in one @Transactional block. It's "all or nothing."&lt;/p&gt;

&lt;p&gt;But in microservices, the &lt;strong&gt;Inventory Service&lt;/strong&gt;, &lt;strong&gt;Payment Service&lt;/strong&gt;,&lt;br&gt;
and &lt;strong&gt;Shipping Service&lt;/strong&gt; each have their own databases. You can't use a&lt;br&gt;
single transaction across network boundaries.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;If the Shipping Service fails after the Payment Service has already&lt;br&gt;
taken the customer's money, how do you fix it&lt;/em&gt;&lt;/strong&gt;? This is where &lt;strong&gt;Saga Design&lt;br&gt;
Pattern&lt;/strong&gt; is useful.&lt;/p&gt;

&lt;h1&gt;
  
  
  1 What is a Saga?
&lt;/h1&gt;

&lt;p&gt;The Saga design pattern helps maintain data consistency in distributed&lt;br&gt;
systems by &lt;strong&gt;coordinating&lt;/strong&gt; transactions across multiple services. A&lt;br&gt;
saga is a sequence of &lt;strong&gt;local transactions&lt;/strong&gt; where each service performs&lt;br&gt;
its operation and initiates the next step through events or messages.&lt;/p&gt;

&lt;p&gt;If a step in the sequence fails, the saga performs &lt;strong&gt;compensating&lt;br&gt;
transactions&lt;/strong&gt; to undo the completed steps. This approach helps maintain&lt;br&gt;
data consistency.&lt;/p&gt;

&lt;p&gt;The Saga Pattern is your distributed &lt;strong&gt;"safety net."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are two ways of coordination sagas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Choreography&lt;/strong&gt; - each local transaction publishes domain events that&lt;br&gt;
trigger local transactions in other services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orchestration&lt;/strong&gt; - an orchestrator (object) tells the participants&lt;br&gt;
what local transactions to execute&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  2 Core Concepts
&lt;/h1&gt;

&lt;h2&gt;
  
  
  2.1 Choreography (The Event-Driven Dance)
&lt;/h2&gt;

&lt;p&gt;There is no central "boss." Services communicate by broadcasting&lt;br&gt;
events.&lt;/p&gt;

&lt;p&gt;Choreography relies on a decentralized "chain reaction" where services&lt;br&gt;
communicate by broadcasting and listening to events via a message broker&lt;br&gt;
like Kafka. There is no central controller; instead, each service knows&lt;br&gt;
exactly which event triggers its local transaction and which event to&lt;br&gt;
emit upon completion. This approach offers high decoupling and no single&lt;br&gt;
point of failure, but it can become difficult to monitor or debug as the&lt;br&gt;
number of services and "spaghetti events" increases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Flow:&lt;/strong&gt; Order Service emits OrderCreated. Payment Service hears
it, charges the card, and emits PaymentSuccessful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Success Chain:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Order Service: Create Order -&amp;gt; Order_Created&lt;/p&gt;

&lt;p&gt;Payment Service: Charge Card -&amp;gt; Payment_Successful&lt;/p&gt;

&lt;p&gt;Inventory Service: Deduct Stock -&amp;gt; Stock_Reserved&lt;/p&gt;

&lt;p&gt;Shipping Service: Ship Package -&amp;gt; Order_Shipped&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure Chain (at Shipping):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Shipping Service: Error -&amp;gt; Shipping_Failed&lt;/p&gt;

&lt;p&gt;Inventory Service: Shipping_Failed heard -&amp;gt; Undo: Restock -&amp;gt;&lt;br&gt;
Stock_Released&lt;/p&gt;

&lt;p&gt;Payment Service: Stock_Released heard -&amp;gt; Undo: Refund -&amp;gt;&lt;br&gt;
Payment_Refunded&lt;/p&gt;

&lt;p&gt;Order Service: Payment_Refunded heard -&amp;gt; Final Status: CANCELLED&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Truly decoupled; no single point of failure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; Hard to "see" the process. It can turn into "Spaghetti&lt;br&gt;
Events" where tracking a single order's journey requires complex&lt;br&gt;
distributed tracing.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2.2 Orchestration (The Centralized Conductor)
&lt;/h2&gt;

&lt;p&gt;A central &lt;strong&gt;Order Orchestrator&lt;/strong&gt; acts as the "Conductor."&lt;/p&gt;

&lt;p&gt;Orchestration uses a central "brain"-typically the Order&lt;br&gt;
Service- to explicitly direct the flow of the entire business process&lt;br&gt;
by sending commands to participant services. The orchestrator manages&lt;br&gt;
the state of the saga, tracks which steps have succeeded, and remains&lt;br&gt;
responsible for triggering specific compensating transactions if a&lt;br&gt;
failure occurs. This pattern is ideal for complex workflows with many&lt;br&gt;
steps, as it provides a single point of visibility and control for the&lt;br&gt;
entire transaction life cycle.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Flow:&lt;/strong&gt; The Orchestrator tells the Payment Service: "Charge the
card." It waits for a "Success" response before telling the
Inventory Service: "Reserve the item."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Success Chain:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Orchestrator -&amp;gt;Charge_Card -&amp;gt;Payment Service (Success)&lt;/p&gt;

&lt;p&gt;Orchestrator -&amp;gt;Reserve_Stock -&amp;gt;Inventory Service (Success)&lt;/p&gt;

&lt;p&gt;Orchestrator -&amp;gt;Ship_Items -&amp;gt;Shipping Service (Success)&lt;/p&gt;

&lt;p&gt;Orchestrator: Mark COMPLETE&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Failure Chain (at Inventory):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Orchestrator -&amp;gt;Reserve_Stock -&amp;gt;Inventory Service (FAIL)&lt;/p&gt;

&lt;p&gt;Orchestrator -&amp;gt;Refund_Money -&amp;gt;Payment Service (Undo)&lt;/p&gt;

&lt;p&gt;Orchestrator: Mark CANCELLED&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt; Single source of truth. Easy to manage complex business&lt;br&gt;
logic.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt; The Orchestrator can become a "God Service" if not&lt;br&gt;
designed with clear boundaries.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2.3 The "Undo" Button: Compensating Transactions
&lt;/h2&gt;

&lt;p&gt;In a distributed system, you cannot "un-commit" a database change. You&lt;br&gt;
must perform a &lt;strong&gt;Semantic Undo&lt;/strong&gt;. Compensating transactions are "undo"&lt;br&gt;
operations designed to restore a system to a consistent state after a&lt;br&gt;
partial failure in a Saga. Unlike a traditional database rollback that&lt;br&gt;
simply deletes an uncommitted change, a compensating transaction is a&lt;br&gt;
new, separate transaction that semantically reverses a previously&lt;br&gt;
committed action---such as issuing a refund for a captured payment or&lt;br&gt;
restocking an item that was previously deducted.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Forward Action&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Compensating Action&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Capture $100&lt;/td&gt;
&lt;td&gt;Refund $100 to customer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inventory&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stock -1&lt;/td&gt;
&lt;td&gt;Stock +1 (Restock)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Shipping&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Create Shipping Label&lt;/td&gt;
&lt;td&gt;Void/Cancel Shipping Label&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  2.4 The Pivot Transaction
&lt;/h2&gt;

&lt;p&gt;Every Saga has a "&lt;strong&gt;Point of No Return&lt;/strong&gt;." In E-commerce, this is&lt;br&gt;
usually &lt;strong&gt;Payment&lt;/strong&gt;. Once the money is taken, the business is committed.&lt;br&gt;
If a failure occurs &lt;em&gt;after&lt;/em&gt; the Pivot (e.g., the shipping printer jams),&lt;br&gt;
we don't refund the user immediately. Instead, we &lt;strong&gt;Retry&lt;/strong&gt; the&lt;br&gt;
shipping service until it succeeds.&lt;/p&gt;

&lt;h1&gt;
  
  
  3 Important Decisions
&lt;/h1&gt;

&lt;h1&gt;
  
  
  3.1 Is Saga pattern suitable
&lt;/h1&gt;

&lt;p&gt;Before you implement a Saga, ask: &lt;strong&gt;"Can I just merge these two&lt;br&gt;
microservices?"&lt;/strong&gt; If two services constantly require a Saga to stay&lt;br&gt;
consistent, they likely belong to the same &lt;strong&gt;Bounded Context&lt;/strong&gt;. Sagas&lt;br&gt;
add significant operational overhead; use them only when domain&lt;br&gt;
separation is strictly required.&lt;/p&gt;

&lt;p&gt;If you do not have clear &lt;strong&gt;"Undo"&lt;/strong&gt; path for every action, then do not&lt;br&gt;
implement Saga.&lt;/p&gt;

&lt;p&gt;If &lt;strong&gt;Longer Gap&lt;/strong&gt; for eventual consistency is acceptable then consider&lt;br&gt;
Periodic Reconciliation instead of Saga. While a &lt;strong&gt;Saga&lt;/strong&gt; proactively&lt;br&gt;
pushes for consistency in milliseconds using a chain of real-time&lt;br&gt;
events, &lt;strong&gt;Periodic Reconciliation&lt;/strong&gt; reactively achieves it by using a&lt;br&gt;
scheduled background job to "sweep" the database and fix&lt;br&gt;
discrepancies. It trades immediate synchronization for a simpler,&lt;br&gt;
self-healing model where time -rather than complex&lt;br&gt;
coordination- ensures all services eventually align.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Saga Pattern (Push-Based)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Periodic Reconciliation (Pull-Based)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trigger Mechanism&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Proactive: Each service "pushes" the next one via real-time events (Kafka/RabbitMQ).&lt;/td&gt;
&lt;td&gt;Reactive: A central job "pulls" records from the DB to find and fix mismatches.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consistency Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Near Real-Time: Syncs in milliseconds to seconds.&lt;/td&gt;
&lt;td&gt;Delayed: Syncs based on job frequency (e.g., every 5, 10, or 60 mins).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Implementation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High Complexity: Requires event brokers, idempotency, and undo logic.&lt;/td&gt;
&lt;td&gt;Low Complexity: Requires a simple scheduler (Spring @Scheduled) and a status-check loop.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failure Handling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fragile: If an event is lost or a service is down, the flow breaks.&lt;/td&gt;
&lt;td&gt;Self-Healing: If a service is down, the job simply retries on the next run.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;When to Use&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High-Scale/High-Velocity: When users need an immediate "Confirmed" screen (e.g., Ride-sharing, seat booking).&lt;/td&gt;
&lt;td&gt;Low-to-Medium Scale: When a 5-minute delay is acceptable for the business (e.g., Invoice generation, shipping updates).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;When to Avoid&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Small Teams: If you don't have the dev-ops resources to manage distributed tracing and message brokers.&lt;/td&gt;
&lt;td&gt;Inventory Scarcity: Avoid if a delay allows "overselling" (e.g., selling the same ticket twice during the 10-minute gap).&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3.2 Orchestration vs. Choreography
&lt;/h2&gt;

&lt;p&gt;Choosing your coordination style is the most critical architectural&lt;br&gt;
decision in a Saga.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;Feature&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Choreography (Event-Driven)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Orchestration (Command-Driven)&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low (initially), High (at scale)&lt;/td&gt;
&lt;td&gt;High (initially), Low (at scale)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Coupling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Loosely coupled&lt;/td&gt;
&lt;td&gt;Orchestrator knows all participants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Best For&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Simple flows (2-3 services)&lt;/td&gt;
&lt;td&gt;Complex flows (5+ services)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Difficult (need distributed tracing)&lt;/td&gt;
&lt;td&gt;Easy (check the Orchestrator log)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  3.3 Where does the Saga live?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Which Microservice "Owns" the Saga?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Choosing the host for your Orchestrator follows the &lt;strong&gt;"Initiator&lt;br&gt;
Rule"&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Intent Owner:&lt;/strong&gt; The service that receives the initial business&lt;br&gt;
intent from the user should host the Saga. In E-commerce, this is the&lt;br&gt;
&lt;strong&gt;Order Service&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Outcome Stakeholder:&lt;/strong&gt; If the "success" of the process is&lt;br&gt;
primarily measured by one domain (e.g., "Was the order placed?"),&lt;br&gt;
that domain should coordinate the steps.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Backend vs. Frontend: Who coordinates?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A common mistake is trying to manage a Saga from the &lt;strong&gt;Frontend&lt;br&gt;
(Angular)&lt;/strong&gt;. &lt;strong&gt;Don't do this.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Backend (Spring Boot) MUST do the Saga.&lt;/strong&gt; If the user closes&lt;br&gt;
their browser or loses internet mid-Saga, an Angular-led process would&lt;br&gt;
die, leaving your data in an inconsistent "zombie" state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Backend&lt;/strong&gt; ensures the process continues even if the user is&lt;br&gt;
offline.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3.4 Role of frontend
&lt;/h2&gt;

&lt;p&gt;While the backend does the heavy lifting, the frontend must handle the&lt;br&gt;
&lt;strong&gt;Asynchronous UX&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Request:&lt;/strong&gt; Angular sends the order and immediately receives a&lt;br&gt;
202 Accepted with a UUID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The Stream:&lt;/strong&gt; The Angular app connects to a &lt;strong&gt;WebSocket&lt;/strong&gt; (via&lt;br&gt;
RxJS) or uses &lt;strong&gt;Server-Sent Events (SSE)&lt;/strong&gt; to listen for the Saga's&lt;br&gt;
progress.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The UI:&lt;/strong&gt; Update the UI dynamically: &lt;em&gt;"Payment Confirmed"&lt;/em&gt; -&amp;gt;&lt;br&gt;
&lt;em&gt;"Stock Reserved"&lt;/em&gt; -&amp;gt; &lt;em&gt;"Success!"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  4 Further Bullet-proofing
&lt;/h1&gt;

&lt;h2&gt;
  
  
  4.1 The Semantic Lock Pattern
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Semantic Lock Pattern&lt;/strong&gt; is a strategy used to handle the "I"&lt;br&gt;
(Isolation) deficiency in the Saga Pattern. Since Sagas are distributed&lt;br&gt;
and take time to complete, they lack the immediate isolation provided by&lt;br&gt;
a traditional ACID database.&lt;/p&gt;

&lt;p&gt;Without this pattern, a system is vulnerable to &lt;strong&gt;"Dirty Reads"&lt;/strong&gt; or&lt;br&gt;
&lt;strong&gt;"Lost Updates"&lt;/strong&gt;---where one process modifies data that a concurrent&lt;br&gt;
process is already using or is about to roll back.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1.1 The Problem: The "Lack of Isolation"
&lt;/h3&gt;

&lt;p&gt;Imagine an E-commerce store with &lt;strong&gt;1 pair of sneakers&lt;/strong&gt; left in stock.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User A&lt;/strong&gt; starts a Saga. The Inventory Service successfully deducts&lt;br&gt;
the last pair.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User B&lt;/strong&gt; arrives a second later. The Inventory Service sees&lt;br&gt;
quantity: 0 and correctly tells User B it's out of stock.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;User A's&lt;/strong&gt; Payment Service then &lt;strong&gt;fails&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Saga triggers a &lt;strong&gt;Compensating Transaction&lt;/strong&gt; to "Undo" the&lt;br&gt;
stock deduction.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Suddenly, the sneakers are back in stock. &lt;strong&gt;User B&lt;/strong&gt; was turned away&lt;br&gt;
for an item that technically became available again, or worse, a&lt;br&gt;
different &lt;strong&gt;User C&lt;/strong&gt; might have seen the "Available" status while&lt;br&gt;
User A was still "failing."&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4.1.2 The Solution: How Semantic Locking Works
&lt;/h3&gt;

&lt;p&gt;Instead of a hard database lock (which would kill performance in&lt;br&gt;
microservices), you use an &lt;strong&gt;application-level status&lt;/strong&gt; to signal that a&lt;br&gt;
record is "busy."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "Pending" State&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When a service performs its local transaction, it doesn't just update&lt;br&gt;
the final value. It marks the record with a &lt;strong&gt;Pending/Locked&lt;/strong&gt; status.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inventory:&lt;/strong&gt; Status becomes RESERVED_PENDING_PAYMENT.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Payment:&lt;/strong&gt; Status becomes AUTHORIZATION_HOLD.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Conflict Handling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While a record has a "Semantic Lock," other transactions must follow&lt;br&gt;
specific rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Read-Only:&lt;/strong&gt; Other users can see the item, but perhaps with a "Low&lt;br&gt;
Stock/In Carts" warning.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Write-Blocked:&lt;/strong&gt; If another Saga tries to buy that same specific&lt;br&gt;
item, the system rejects the request or puts it in a queue because the&lt;br&gt;
record is "Locked."&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Success Path:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inventory Service&lt;/strong&gt;: Sets status to PENDING_COMMIT.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Saga Finishes&lt;/strong&gt;: Orchestrator sends a "Finalize" command.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inventory Service&lt;/strong&gt;: Clears the lock and sets status to SOLD.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Failure Path (The Rollback):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Shipping Service&lt;/strong&gt;: Fails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Orchestrator&lt;/strong&gt;: Sends a "Compensate" command to Inventory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Inventory Service&lt;/strong&gt;: Sees the PENDING_COMMIT lock and simply changes&lt;br&gt;
it back to AVAILABLE.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Clarity Note:&lt;/strong&gt; This prevents "Dirty Reads" because any other&lt;br&gt;
service looking at that item knew it wasn't &lt;em&gt;really&lt;/em&gt; gone yet---it was&lt;br&gt;
just "Semantically Locked."&lt;/p&gt;

&lt;h2&gt;
  
  
  4.2 Combining with Transactional Outbox Pattern
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;What if your service updates the DB but crashes before it can send the&lt;br&gt;
Kafka event?" Or What if Kafka event is sent but transaction is&lt;br&gt;
rollbacked&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To solve this, use the &lt;strong&gt;Transactional Outbox Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Instead of sending an event directly to Kafka, save the event into&lt;br&gt;
an OUTBOX table in the &lt;strong&gt;same&lt;/strong&gt; transaction as your business data.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A background process polls the outbox and pushes messages to the&lt;br&gt;
broker.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  5 Final Checklist for Production
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Idempotency:&lt;/strong&gt; Can your services handle the same event twice?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Observability:&lt;/strong&gt; Do you have a &lt;strong&gt;correlation-id&lt;/strong&gt; passing through&lt;br&gt;
all services?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compensations:&lt;/strong&gt; Does every "Do" action have a corresponding&lt;br&gt;
"Undo" action?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  6 Summary
&lt;/h1&gt;

&lt;p&gt;Sagas are about &lt;strong&gt;Resilience&lt;/strong&gt;. By using an &lt;strong&gt;Orchestrator&lt;/strong&gt; in the &lt;strong&gt;Backend&lt;/strong&gt;,&lt;br&gt;
implementing &lt;strong&gt;Semantic Locks&lt;/strong&gt;, &lt;strong&gt;Transactional Outbox&lt;/strong&gt;, and keeping the &lt;strong&gt;Frontend reactive&lt;/strong&gt;, you build a system that can gracefully handle the chaos of distributed&lt;br&gt;
networks.&lt;/p&gt;

&lt;h1&gt;
  
  
  References
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://microservices.io/patterns/data/saga.html" rel="noopener noreferrer"&gt;https://microservices.io/patterns/data/saga.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/patterns/saga" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/architecture/patterns/saga&lt;/a&gt;&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>saga</category>
      <category>consistency</category>
      <category>pattern</category>
    </item>
  </channel>
</rss>
