<?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>Forem: Mohamed Hassan</title>
    <description>The latest articles on Forem by Mohamed Hassan (@hassan314159).</description>
    <link>https://forem.com/hassan314159</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%2F3458856%2F368cd825-a5ad-4251-9ccf-d6f8bd71c299.png</url>
      <title>Forem: Mohamed Hassan</title>
      <link>https://forem.com/hassan314159</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hassan314159"/>
    <language>en</language>
    <item>
      <title>Simply Order (Part 9) — CQRS Pattern: Separating Reads from Writes for Better Performance</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Mon, 08 Dec 2025 22:57:16 +0000</pubDate>
      <link>https://forem.com/hassan314159/simply-order-part-9-cqrs-pattern-separating-reads-from-writes-for-better-performance-434</link>
      <guid>https://forem.com/hassan314159/simply-order-part-9-cqrs-pattern-separating-reads-from-writes-for-better-performance-434</guid>
      <description>&lt;p&gt;This is the ninth article in our series, where we design a simple order solution for a hypothetical company called &lt;strong&gt;Simply Order&lt;/strong&gt;. The company expects high traffic and needs a resilient, scalable, and distributed order system.&lt;/p&gt;

&lt;p&gt;In the previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/distributed-transactions-in-microservices-why-2pc-doesnt-fit-and-how-sagas-help-1lb"&gt;Simply Order (Part 1) Distributed Transactions in Microservices: Why 2PC Doesn't Fit and How Sagas Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23"&gt;Simply Order (Part 2) — Designing and Implementing the Saga Workflow with Temporal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe"&gt;Simply Order (Part 3) — Linking It All Together: Connecting Services and Watching Temporal in Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-4-reliable-events-with-the-outbox-pattern-concepts-55ko"&gt;Simply Order (Part 4) — Reliable Events with the Outbox Pattern (Concepts)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-5-hands-on-building-the-outbox-pattern-for-reliable-event-60n"&gt;Simply Order (Part 5) — Hands-On: Building the Outbox Pattern for Reliable Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-6-making-apis-idempotent-because-users-double-click-and-networks-lie-1826"&gt;Simply Order (Part 6) – Making APIs Idempotent: Because Users Double-Click and Networks Lie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-7-querying-orders-with-details-api-composition-pattern-20"&gt;Simply Order (Part 7) – Querying Orders with Details: API Composition Pattern&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-8-querying-orders-with-details-graphql-in-action-2nmb"&gt;Simply Order (Part 8) — Building GraphQL API Service with spring-graphql&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built the core services with distributed transactions, reliable event handling, and idempotent operations. We also created a GraphQL API service to compose queries across multiple microservices.&lt;/p&gt;

&lt;p&gt;However, we've encountered a hidden performance problem. Let's explore it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Imagine a typical order workflow in &lt;strong&gt;Simply Order&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A customer places an order.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Order Service&lt;/strong&gt; writes the order to its database.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Payment Service&lt;/strong&gt; processes the payment.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Inventory Service&lt;/strong&gt; reserves items.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;GraphQL API&lt;/strong&gt; queries multiple services to return order details to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works fine, but consider what happens during peak traffic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hundreds of orders are being &lt;strong&gt;written&lt;/strong&gt; simultaneously.&lt;/li&gt;
&lt;li&gt;Simultaneously, customers are &lt;strong&gt;querying&lt;/strong&gt; their orders, shipments, and payment statuses.&lt;/li&gt;
&lt;li&gt;The database is now handling both heavy write operations (inserts, updates) and read operations that fetch complex joins across tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? &lt;strong&gt;Database contention&lt;/strong&gt;. Reads are blocked by writes. Writes compete for locks. Queries become slow. Your system feels sluggish.&lt;/p&gt;

&lt;p&gt;🤔 What if we could optimize writes and reads separately?&lt;/p&gt;

&lt;h2&gt;
  
  
  CQRS: Command Query Responsibility Segregation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CQRS&lt;/strong&gt; is a pattern that separates the model for updating data (writes) from the model for reading data (reads). Instead of having one unified model that handles both operations, you maintain two separate models optimized for their specific purpose.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Idea
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Commands:&lt;/strong&gt; Operations that modify data (create, update, delete). They write to an optimized write model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queries:&lt;/strong&gt; Operations that fetch data. They read from an optimized read model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two models are kept in sync through events — typically the same events we are already publishing via the &lt;strong&gt;Outbox Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Independent Scalability&lt;/strong&gt;: Scale writes and reads independently. Add more read replicas without affecting write throughput.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Optimization&lt;/strong&gt;: The write database can be optimized for transactions and consistency. The read database can be optimized for queries and reporting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified Queries&lt;/strong&gt;: Instead of complex joins and aggregations, read models store denormalized, query-optimized data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event Sourcing Ready&lt;/strong&gt;: CQRS pairs naturally with event sourcing for complete audit trails.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  CQRS Approaches
&lt;/h2&gt;

&lt;p&gt;There are different ways to implement CQRS, ranging from simple to complex:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Separate Repositories (Simple CQRS)
&lt;/h3&gt;

&lt;p&gt;In this lightweight approach, you maintain &lt;strong&gt;two separate repositories&lt;/strong&gt; within the same service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write Repository&lt;/strong&gt;: Optimized for transactions and consistency. Used during command execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read Repository&lt;/strong&gt;: Optimized for queries. Used during read operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both repositories can use the same database or different databases — the key is logical separation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple to implement.&lt;/li&gt;
&lt;li&gt;No additional infrastructure required.&lt;/li&gt;
&lt;li&gt;Great starting point for CQRS.&lt;/li&gt;
&lt;li&gt;Perfect for services that don't have extreme read/write asymmetry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Limited separation of concerns.&lt;/li&gt;
&lt;li&gt;Both models live in the same process.&lt;/li&gt;
&lt;li&gt;Scaling reads independently is harder without external infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Different Data Sources (Complex CQRS)
&lt;/h3&gt;

&lt;p&gt;In this approach, the write and read models use &lt;strong&gt;completely different databases or data sources&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write Side&lt;/strong&gt;: Transactional database (e.g., PostgreSQL) optimized for ACID compliance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read Side&lt;/strong&gt;: Search or analytics database (e.g., Elasticsearch, DynamoDB, or a data warehouse).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The read model is populated asynchronously through events published by the write side.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Complete separation of concerns.&lt;/li&gt;
&lt;li&gt;True independent scalability.&lt;/li&gt;
&lt;li&gt;Write model can focus on transactions; read model can focus on performance.&lt;/li&gt;
&lt;li&gt;Read replicas can be geo-distributed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Increased complexity.&lt;/li&gt;
&lt;li&gt;Eventual consistency — reads might be slightly stale.&lt;/li&gt;
&lt;li&gt;Requires event infrastructure to keep models in sync.&lt;/li&gt;
&lt;li&gt;Operational overhead for multiple databases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementation: Our Approach
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;Simply Order&lt;/strong&gt;, we've implemented a &lt;strong&gt;simple CQRS approach&lt;/strong&gt; using separate logical repositories with same model:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code for this project can be found in this repository:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/hassan314159/simply-order" rel="noopener noreferrer"&gt;https://github.com/hassan314159/simply-order&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this repository is continuously updated, the code specific to this lesson can be found in the &lt;em&gt;implment_cqrs_for_order_and_inventory&lt;/em&gt; branch. Start with:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout implment_cqrs_for_order_and_inventory
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We divided the &lt;strong&gt;Order Service&lt;/strong&gt; into two distinct repositories:&lt;/p&gt;

&lt;h3&gt;
  
  
  Write Repository (Command Side)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderEntity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;&amp;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;p&gt;This repository handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating new orders&lt;/li&gt;
&lt;li&gt;Updating order status during Saga workflow&lt;/li&gt;
&lt;li&gt;Committing changes to the transactional database&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Read Repository (Query Side)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderSearchRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderEntity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Query&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"""
        select distinct o from OrderEntity o
        left join o.items i
        where (:customerId is null or o.customerId = :customerId)
          and (:status     is null or o.status = :status)
          and (:fromDate   is null or o.createdAt &amp;gt;= :fromDate)
          and (:toDate     is null or o.createdAt &amp;lt; :toDate)
          and (:sku        is null or i.sku = :sku)
        order by o.createdAt desc
        """&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customerId"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"fromDate"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;OffsetDateTime&lt;/span&gt; &lt;span class="n"&gt;fromDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"toDate"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;OffsetDateTime&lt;/span&gt; &lt;span class="n"&gt;toDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nd"&gt;@Param&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sku"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sku&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;p&gt;This repository handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fetching orders for a customer&lt;/li&gt;
&lt;li&gt;Searching orders by status&lt;/li&gt;
&lt;li&gt;Filtering by date ranges&lt;/li&gt;
&lt;li&gt;All read operations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Service Layer
&lt;/h3&gt;

&lt;p&gt;we have split the service layer logically into two&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;OrderCommandService&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OrderQueryService&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hassan314159/simply-order/blob/implment_cqrs_for_order_and_inventory/order-service/src/main/java/dev/simplyoder/order/service/command/OrderCommandService.java" rel="noopener noreferrer"&gt;OrderCommandService.java&lt;/a&gt;&lt;/strong&gt;&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;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderCommandService&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="no"&gt;UUID&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;JsonProcessingException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;OrderEntity&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&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="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&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="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="nc"&gt;OutboxEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pending&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OrderCreated"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderId&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;p&gt;&lt;strong&gt;&lt;a href="https://github.com/hassan314159/simply-order/blob/implment_cqrs_for_order_and_inventory/order-service/src/main/java/dev/simplyoder/order/service/query/OrderQueryService.java" rel="noopener noreferrer"&gt;OrderQueryService.java&lt;/a&gt;&lt;/strong&gt;&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;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderQueryService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;OrderSearchRepository&lt;/span&gt; &lt;span class="n"&gt;orderSearchRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;OrderMapper&lt;/span&gt; &lt;span class="n"&gt;orderMapper&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderQueryService&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderSearchRepository&lt;/span&gt; &lt;span class="n"&gt;orderSearchRepository&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderMapper&lt;/span&gt; &lt;span class="n"&gt;orderMapper&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderSearchRepository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderSearchRepository&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderMapper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderMapper&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findByOrderId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderSearchRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;orderMapper:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;toDto&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OffsetDateTime&lt;/span&gt; &lt;span class="n"&gt;fromDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OffsetDateTime&lt;/span&gt; &lt;span class="n"&gt;toDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderSearchRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fromDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;toDate&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;orderMapper:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;toDto&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&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;p&gt;Same have been applied to &lt;strong&gt;inventory repository&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Homework
&lt;/h2&gt;

&lt;p&gt;Our current implementation uses separate repositories logically but still using same tables and database. The next evolution would be to:&lt;/p&gt;

&lt;h3&gt;
  
  
  Evolution Path
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Starting Simple: Separate the Model
&lt;/h4&gt;

&lt;p&gt;By separating the model — i.e., having separate tables and entities for Search, so we can gain multiple benefits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Easier to optimize each side independently.&lt;/li&gt;
&lt;li&gt;Better testing — you can test write and read logic separately.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keeping Models in Sync&lt;/strong&gt;: When an order status changes (via the Saga workflow), we can simply update both repositories within &lt;code&gt;updateOrderStatus()&lt;/code&gt;. We can also isolate the updates of the two repositories by sending an event or using event &lt;strong&gt;outbox pattern&lt;/strong&gt; and create a relay that updates &lt;strong&gt;OrderStatus&lt;/strong&gt; in &lt;strong&gt;OrderSearchRepository&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Evolving to Separate Data Sources
&lt;/h4&gt;

&lt;p&gt;As your system grows, you might migrate to completely separate databases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Extract the read model to a dedicated read database (e.g., PostgreSQL read replica or Elasticsearch).&lt;/li&gt;
&lt;li&gt;Publish order events to Kafka when orders are created or updated.&lt;/li&gt;
&lt;li&gt;Build a read service that consumes these events and populates the read database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This would be a full-fledged CQRS implementation with separate data sources. Give it a try — or consider the trade-offs and decide if your system really needs that level of separation.&lt;/p&gt;

&lt;p&gt;This gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;True independent scaling.&lt;/li&gt;
&lt;li&gt;Ability to optimize read databases for your specific query patterns.&lt;/li&gt;
&lt;li&gt;Geo-distributed read replicas.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it also introduces complexity — you'll need to handle eventual consistency and synchronization failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;In this lesson, we introduced the &lt;strong&gt;CQRS pattern&lt;/strong&gt; and showed that it doesn't have to be complex. By simply separating our repositories into write and read models, we've already gained the benefits of CQRS: independent optimization, clearer code structure, and a path forward for scaling.&lt;/p&gt;

&lt;p&gt;CQRS pairs beautifully with the &lt;strong&gt;Outbox Pattern&lt;/strong&gt; &lt;br&gt;
— a pattern you've already seen in previous articles. Together, they form the backbone of scalable, reliable distributed systems.&lt;/p&gt;

&lt;p&gt;The beauty of CQRS is that you can start simple (separate repositories) and evolve to more complex implementations (separate databases) only when you need to. Don't over-engineer from day one — understand your read/write patterns first, then evolve.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>microservices</category>
      <category>designpatterns</category>
      <category>cqrs</category>
    </item>
    <item>
      <title>Simply Order (Part 8) – Querying Orders with Details: GraphQL in Action</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Mon, 01 Dec 2025 18:20:23 +0000</pubDate>
      <link>https://forem.com/hassan314159/simply-order-part-8-querying-orders-with-details-graphql-in-action-2nmb</link>
      <guid>https://forem.com/hassan314159/simply-order-part-8-querying-orders-with-details-graphql-in-action-2nmb</guid>
      <description>&lt;p&gt;This is the eighth article in our series, where we design a simple order solution for a hypothetical company called &lt;strong&gt;Simply Order&lt;/strong&gt;. The company expects high traffic and needs a resilient, scalable, and distributed order system.&lt;/p&gt;

&lt;p&gt;In the previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/distributed-transactions-in-microservices-why-2pc-doesnt-fit-and-how-sagas-help-1lb"&gt;Simply Order (Part 1) Distributed Transactions in Microservices: Why 2PC Doesn’t Fit and How Sagas Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23"&gt;Simply Order (Part 2) — Designing and Implementing the Saga Workflow with Temporal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe"&gt;Simply Order (Part 3) — Linking It All Together: Connecting Services and Watching Temporal in Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-4-reliable-events-with-the-outbox-pattern-concepts-55ko"&gt;Simply Order (Part 4) — Reliable Events with the Outbox Pattern (Concepts)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-5-hands-on-building-the-outbox-pattern-for-reliable-event-60n"&gt;Simply Order (Part 5) — Hands-On: Building the Outbox Pattern for Reliable Events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-6-making-apis-idempotent-because-users-double-click-and-networks-lie-1826"&gt;Simply Order (Part 6) – Making APIs Idempotent: Because Users Double-Click and Networks Lie&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-7-querying-orders-with-details-api-composition-pattern-20"&gt;Simply Order (Part 7) – Querying Orders with Details: API Composition Pattern&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the previous lesson, we discussed the API composition pattern and how it solves the issue of querying multiple services. In this article, we will implement a new service to act as our API composer using spring-graphql, which runs a GraphQL server exposing data from multiple microservices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The code for this project can be found in this repository:&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/hassan314159/simply-order" rel="noopener noreferrer"&gt;https://github.com/hassan314159/simply-order&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this repository is continuously updated, the code specific to this lesson can be found in the &lt;em&gt;add_graphql_api&lt;/em&gt; branch. Start with:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout add_graphql_api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will use &lt;a href="https://spring.io/projects/spring-graphql" rel="noopener noreferrer"&gt;&lt;code&gt;spring-graphql&lt;/code&gt;&lt;/a&gt; to build our service.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The spring-graphql dependency is the official Spring project for building GraphQL APIs in Spring Boot applications. With this dependency, we can define GraphQL schemas, implement resolvers as annotated Java methods, and expose a single &lt;code&gt;/graphql&lt;/code&gt; endpoint for all queries and mutations. It also includes features like schema introspection and batching (to solve N+1 problems), making it simple to build flexible, type-safe APIs backed by your existing data sources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Data Flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Components of GraphQL
&lt;/h3&gt;

&lt;p&gt;In the previous article, we saw that GraphQL consists of these components:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Language&lt;/strong&gt;: A query language letting clients request exactly the data they need.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema&lt;/strong&gt;: Defines available data types, fields, and operations (queries, mutations, subscriptions).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server&lt;/strong&gt;: Processes queries, validates them against the schema, and returns results.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resolvers&lt;/strong&gt;: Functions that fetch data for each field in the schema.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data Sources&lt;/strong&gt;: Databases, APIs, or services where the actual data resides.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The data flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8pw5bywmvzd0ugv4mjw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8pw5bywmvzd0ugv4mjw.png" alt="Sequence Diagram" width="764" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to Build
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Server: The Execution Engine
&lt;/h3&gt;

&lt;p&gt;The GraphQL server receives queries, validates them against the schema, and orchestrates data fetching through resolvers. Add the &lt;code&gt;spring-graphql&lt;/code&gt; dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.springframework.graphql&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;spring-graphql&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see it here: &lt;a href="https://github.com/hassan314159/simply-order/blob/master/graphql-api-service/pom.xml#L24-L27" rel="noopener noreferrer"&gt;pom.xml&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema: The API Contract
&lt;/h3&gt;

&lt;p&gt;The schema defines all available data types, fields, and operations (queries, mutations, subscriptions) your API supports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema components:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Types:&lt;/strong&gt; Define the shape of data (e.g., &lt;code&gt;User&lt;/code&gt;, &lt;code&gt;Order&lt;/code&gt;, &lt;code&gt;Product&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fields:&lt;/strong&gt; Properties of each type (e.g., User has &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;id&lt;/code&gt;)
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Query&lt;/code&gt;: Fetch data (read-only)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Mutation&lt;/code&gt;: Modify data (create, update, delete)
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Subscription&lt;/code&gt;: Real-time updates via WebSocket
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;View the schema here: &lt;a href="https://github.com/hassan314159/simply-order/blob/master/graphql-api-service/src/main/resources/graphql/schema.graphqls" rel="noopener noreferrer"&gt;schema.graphqls&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Query&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="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;ordersByCustomer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&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="mi"&gt;20&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="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;!]!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!):&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InventoryItem&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Order&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="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;items&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="n"&gt;OrderItem&lt;/span&gt;&lt;span class="p"&gt;!]!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;totalAmount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Float&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="k"&gt;enum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderStatus&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="n"&gt;NEW&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;PROCESSING&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;SHIPPED&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;CANCELLED&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;FAILED&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;COMPLETED&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OrderItem&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="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;quantity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;inventory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InventoryItem&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="k"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;InventoryItem&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="n"&gt;sku&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;availableQty&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Int&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;blockquote&gt;
&lt;p&gt;By defining this schema, we establish the contract and view for clients. For example, we can hide unnecessary or domain-specific fields. The &lt;code&gt;InventoryItem&lt;/code&gt; type exposes an &lt;code&gt;availableQty&lt;/code&gt; field while hiding implementation details like &lt;code&gt;stock&lt;/code&gt; and &lt;code&gt;reserved&lt;/code&gt; in the inventory service.  &lt;/p&gt;

&lt;p&gt;Also, we expose only the important statuses of &lt;code&gt;OrderStatus&lt;/code&gt;, mapping user non-related statuses to meaningful ones (e.g., mapping &lt;code&gt;OPEN&lt;/code&gt; &amp;amp; &lt;code&gt;PENDING&lt;/code&gt; to just &lt;code&gt;OPEN&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Java classes mapped to the types are here: &lt;a href="https://github.com/hassan314159/simply-order/tree/master/graphql-api-service/src/main/java/dev/simplyoder/api/api/model" rel="noopener noreferrer"&gt;model package&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Sources: The Information Layer
&lt;/h3&gt;

&lt;p&gt;Data sources are where data actually lives—databases, REST APIs, microservices, files, etc.&lt;/p&gt;

&lt;p&gt;In our example, the data sources are OrderDB and InventoryDB. We don't call them directly from this microservice; instead, we use HTTP clients to query these services.  &lt;/p&gt;

&lt;p&gt;Http clients and data models are here: &lt;a href="https://github.com/hassan314159/simply-order/tree/master/graphql-api-service/src/main/java/dev/simplyoder/api/infra/client" rel="noopener noreferrer"&gt;client package&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Resolvers: The Data Fetchers
&lt;/h3&gt;

&lt;p&gt;Resolvers fetch or compute data for specific fields in your schema.&lt;/p&gt;

&lt;p&gt;We have 2 resolvers in our service:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;OrderQueryController.java&lt;/code&gt;: Query resolvers for root-level queries, annotated with &lt;code&gt;@Controller&lt;/code&gt; and &lt;code&gt;@QueryMapping&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;OrderItemFieldResolver.java&lt;/code&gt;: Field resolvers for nested fields, handling the N+1 problem via &lt;code&gt;@BatchMapping&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Resolver code: &lt;a href="https://github.com/hassan314159/simply-order/blob/master/graphql-api-service/src/main/java/dev/simplyoder/api/api/OrderQueryController.java" rel="noopener noreferrer"&gt;OrderQueryController.java&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/hassan314159/simply-order/blob/master/graphql-api-service/src/main/java/dev/simplyoder/api/api/OrderItemFieldResolver.java" rel="noopener noreferrer"&gt;OrderItemFieldResolver.java&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Query Resolver Example
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@QueryMapping&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;order&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Argument&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;switchIfEmpty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;error&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;RuntimeException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Order not found"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;orderMapper:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;toModel&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;h4&gt;
  
  
  Batch Resolver: Solving the N+1 Problem
&lt;/h4&gt;

&lt;p&gt;To resolve nested fields like &lt;code&gt;inventory&lt;/code&gt; on &lt;code&gt;OrderItem&lt;/code&gt;, instead of querying the inventory service for each item (N queries), we use batching to fetch all required SKUs in one query:&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;@BatchMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;typeName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"OrderItem"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"inventory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Mono&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;InventoryItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;inventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&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;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;skus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;OrderItem:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;inventoryClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBySkus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skus&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventoryDtos&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Map&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;InventoryItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bySku&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inventoryDtos&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;inventoryMapper:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;toModel&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;InventoryItem:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;inv&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

                &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItem&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;InventoryItem&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;result&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;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderItem&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bySku&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&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;
  
  
  Language: Query What You Need
&lt;/h3&gt;

&lt;p&gt;GraphQL query language allows clients to specify exactly the data they want.&lt;/p&gt;

&lt;p&gt;Sample query to fetch an order by &lt;code&gt;id&lt;/code&gt; with its details:&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;"query"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"query { order(id: "&lt;/span&gt;&lt;span class="err"&gt;ORD&lt;/span&gt;&lt;span class="mi"&gt;-1001&lt;/span&gt;&lt;span class="s2"&gt;") { id customerId createdAt status totalAmount items { sku quantity unitPrice } } }"&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;The outer &lt;code&gt;query&lt;/code&gt; key contains the GraphQL operation string. Inside, the nested &lt;code&gt;query&lt;/code&gt; specifies the fetch operation. Curly braces specify which fields to retrieve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Homework
&lt;/h2&gt;

&lt;p&gt;We have implemented the fetch operations needed for our business. However, currently, we must call the &lt;strong&gt;Order&lt;/strong&gt; service directly to create orders. Can you create a &lt;code&gt;mutation&lt;/code&gt; operation in our API service to provide a single entry point for all client interactions?&lt;/p&gt;




&lt;h3&gt;
  
  
  Wrap-up
&lt;/h3&gt;

&lt;p&gt;In this article, the GraphQL API service was introduced as an effective API composition layer for the Simply Order system. Using spring-graphql, we defined a schema that exposes consolidated views from multiple microservices, hiding internal complexities and mapping data meaningfully for clients. We implemented query resolvers with efficient batch loading to solve common performance problems like the N+1 query issue. This design enables a scalable, flexible, and type-safe API interface that fits the needs of a high-traffic distributed order system.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>distributedsystems</category>
      <category>api</category>
    </item>
    <item>
      <title>Simply Order (Part 7) – Querying Orders with Details: API Composition Pattern</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Wed, 19 Nov 2025 21:24:06 +0000</pubDate>
      <link>https://forem.com/hassan314159/simply-order-part-7-querying-orders-with-details-api-composition-pattern-20</link>
      <guid>https://forem.com/hassan314159/simply-order-part-7-querying-orders-with-details-api-composition-pattern-20</guid>
      <description>&lt;p&gt;This is the seventh article in our series, where we design a simple order solution for a hypothetical company called &lt;strong&gt;Simply Order&lt;/strong&gt;. The company expects high traffic and needs a resilient, scalable, and distributed order system.&lt;/p&gt;

&lt;p&gt;In the previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/distributed-transactions-in-microservices-why-2pc-doesnt-fit-and-how-sagas-help-1lb"&gt;Simply Order (Part 1) Distributed Transactions in Microservices: Why 2PC Doesn’t Fit and How Sagas Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23"&gt;Simply Order (Part 2) — Designing and Implementing the Saga Workflow with Temporal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe"&gt;Simply Order (Part 3) — Linking It All Together: Connecting Services and Watching Temporal in Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-4-reliable-events-with-the-outbox-pattern-concepts-55ko"&gt;Simply Order (Part 4) — Reliable Events with the Outbox Pattern (Concepts)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-5-hands-on-building-the-outbox-pattern-for-reliable-event-60n"&gt;Simply Order (Part 5) — Hands-On: Building the Outbox Pattern for Reliable Event&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-6-making-apis-idempotent-because-users-double-click-and-networks-lie-1826"&gt;Simply Order (Part 6) – Making APIs Idempotent: Because Users Double-Click and Networks Lie&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In previous lessons, we built core services like Order, Payment, and Inventory, focusing on making our distributed transactions reliable using Sagas, the Outbox pattern, and Idempotency.&lt;/p&gt;

&lt;p&gt;Now imagine the business comes with a very simple requirement:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;On the Order Details page, show the order with its items, and for each item show the current stock status so customers know if they can re‑order.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;From a microservices perspective, our system looks like this: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Order Service&lt;/strong&gt; owns orders and items by their SKU.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inventory Service&lt;/strong&gt; owns item details along with stock and availability per item.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each service has its own bounded context, but users need to see their orders with the corresponding item details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 1: Direct client-to-microservice communication
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1yfcxlcb71eipkqqzgg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz1yfcxlcb71eipkqqzgg.png" alt="Direct client-to-microservice" width="601" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the simplest and most naïve solution: the frontend talks directly to the services:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call &lt;code&gt;GET /orders/{id} from&lt;/code&gt; the Order service.​&lt;/li&gt;
&lt;li&gt;For each item in order, call &lt;code&gt;GET /inventory/{sku}&lt;/code&gt; from the Inventory service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This leads to several problems:​&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The frontend suddenly “knows” about internal service boundaries.&lt;/li&gt;
&lt;li&gt;Latency explodes; a single order fetch could trigger dozens of calls based on the number of items, known as the N+1 Problem (1 order + N items).​&lt;/li&gt;
&lt;li&gt;The frontend becomes tightly coupled with internal service APIs, so any change in service APIs requires changes in all frontend clients (webpages, mobile apps, third parties, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, this approach is unsuitable for a continuously evolving business and APIs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution 2: API Composition
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcwdv0n61dmaaeam8wxy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhcwdv0n61dmaaeam8wxy.png" alt="API Composition" width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this approach we move the complexity of aggregating responses from different services into a separate service often called the &lt;strong&gt;Aggregator/Composer Service&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The frontend sends requests to the &lt;strong&gt;Composer Service&lt;/strong&gt;. &lt;/li&gt;
&lt;li&gt;The Composer Service calls multiple microservices (Order, Inventory, etc.).&lt;/li&gt;
&lt;li&gt;It collects, transforms, and merges data into a unified result, returning it to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this approach, we can overcome the problems of the naive approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Internal service boundaries are hidden from the client API.&lt;/li&gt;
&lt;li&gt;The aggregator service decouples the frontend from internal APIs, so even with evolving APIs, we can preserve our contract with clients.&lt;/li&gt;
&lt;li&gt; With a smart implementation, we can optimize the &lt;strong&gt;N+1&lt;/strong&gt; problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Simple custom implementation
&lt;/h3&gt;

&lt;p&gt;We build a thin service called Composer or Aggregator that, for each view of our data, provides a REST endpoint which aggregates all necessary endpoints and returns the corresponding result.&lt;/p&gt;

&lt;p&gt;Some minor drawbacks of this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have to build separate endpoints for each view of our data. For example, if some pages need different details, we may need to create different endpoints to handle those.&lt;/li&gt;
&lt;li&gt;It can get more sophisticated with retries, parallelism, mapping, and error handling.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API Composition with GraphQL
&lt;/h3&gt;

&lt;p&gt;As mentioned in &lt;a href="https://graphql.org/" rel="noopener noreferrer"&gt;GraphQL&lt;/a&gt; official Page&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GraphQL is an open‑source query language for APIs and a server‑side runtime. It provides a strongly‑typed schema to define relationships between data, making APIs more flexible and predictable. And it isn’t tied to a specific database or storage engine — it works with your existing code and data, making it easier to evolve APIs over time. &lt;/p&gt;

&lt;p&gt;GraphQL serves as a unified data layer across multiple services. This way you simplify API management and reduce dependencies between teams. It enables efficient data fetching while keeping the API surface flexible and maintainable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GraphQL is built around a few core components that work together to let clients request exactly the data they need from a single endpoint. Here’s a simple introduction to each main part:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Language: A query language letting clients request exactly the data they need.&lt;/li&gt;
&lt;li&gt;Schema: Defines available data types, fields, and operations (queries, mutations, subscriptions).&lt;/li&gt;
&lt;li&gt;Server: Processes queries, validates them against the schema, and returns results.&lt;/li&gt;
&lt;li&gt;Resolvers: Functions that fetch data for each field in the schema.&lt;/li&gt;
&lt;li&gt;Data Sources: Databases, APIs, or services where the actual data lives.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these components allow GraphQL to unify data from multiple sources into a single, flexible API.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rather than custom endpoints for each composite view, GraphQL exposes a type-based schema that lets clients specify exactly what data they want.&lt;/li&gt;
&lt;li&gt;Resolvers act as the composition code—each field in the schema calls the appropriate microservice, then GraphQL merges all results for the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;GraphQL can be implemented with different languages and frameworks. Spring Boot provides first-class support for GraphQL, making it easy to build type-safe, efficient APIs in Java.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warp Up
&lt;/h2&gt;

&lt;p&gt;In this article we moved from naive client‑to‑microservice calls to a proper composition layer, and then saw how GraphQL naturally fits as a unified API across our Order and Inventory services. Instead of creating custom endpoints for every page, a strongly‑typed schema and resolvers let clients ask for exactly the data they need from a single endpoint, without leaking internal boundaries.​&lt;/p&gt;

&lt;p&gt;In the next article, we’ll take this one step further and implement this GraphQL composition layer using spring‑graphql on top of our existing microservices. We’ll design the schema around the “Order Details” use case, wire resolvers to Order and Inventory, and see how to handle cross‑service calls, errors, and performance concerns in a clean, type‑safe way&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>api</category>
      <category>microservices</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Simply Order (Part 6) – Making APIs Idempotent: Because Users Double-Click and Networks Lie</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Tue, 04 Nov 2025 07:05:58 +0000</pubDate>
      <link>https://forem.com/hassan314159/simply-order-part-6-making-apis-idempotent-because-users-double-click-and-networks-lie-1826</link>
      <guid>https://forem.com/hassan314159/simply-order-part-6-making-apis-idempotent-because-users-double-click-and-networks-lie-1826</guid>
      <description>&lt;p&gt;This is the fifth article in our series, where we design a simple order solution for a hypothetical company called &lt;strong&gt;Simply Order&lt;/strong&gt;. The company expects high traffic and needs a resilient, scalable, and distributed order system.&lt;/p&gt;

&lt;p&gt;In the previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/distributed-transactions-in-microservices-why-2pc-doesnt-fit-and-how-sagas-help-1lb"&gt;Simply Order (Part 1) Distributed Transactions in Microservices: Why 2PC Doesn’t Fit and How Sagas Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23"&gt;Simply Order (Part 2) — Designing and Implementing the Saga Workflow with Temporal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe"&gt;Simply Order (Part 3) — Linking It All Together: Connecting Services and Watching Temporal in Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-4-reliable-events-with-the-outbox-pattern-concepts-55ko"&gt;Simply Order (Part 4) — Reliable Events with the Outbox Pattern (Concepts)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-5-hands-on-building-the-outbox-pattern-for-reliable-event-60n"&gt;Simply Order (Part 5) — Hands-On: Building the Outbox Pattern for Reliable Event&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built the core services — Order, Payment, and Inventory — and discussed different approaches for handling distributed transactions across multiple services. Then, we designed and implemented the Saga workflow. Next, we introduced the problem of dual-write consistency and how the Outbox Pattern can solve it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;In this article, we’re tackling another critical issue in the &lt;strong&gt;inventory service&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Imagine you place an order, and somehow the same item gets reserved twice. Could that happen?  &lt;/p&gt;

&lt;p&gt;Short answer: &lt;strong&gt;YES.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;In distributed systems, retries are inevitable. The problem is that your service doesn’t always know whether the first request succeeded or failed. For example, our Saga workflow might retry a &lt;em&gt;reserve order&lt;/em&gt; request due to a network delay or a temporary failure — even though the inventory service already reserved the items the first time. &lt;/p&gt;
&lt;h2&gt;
  
  
  Idempotency Definition
&lt;/h2&gt;

&lt;p&gt;So, what exactly does &lt;strong&gt;idempotency&lt;/strong&gt; mean?  &lt;/p&gt;

&lt;p&gt;An API is called &lt;em&gt;idempotent&lt;/em&gt; when calling it multiple times with the same input produces the same result — or, more precisely, the same &lt;strong&gt;server-side effect&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
In other words, making the same request several times leaves the system in the same state.  &lt;/p&gt;

&lt;p&gt;For example:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GET&lt;/strong&gt;: Fetches data — the server state never changes.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PUT&lt;/strong&gt;: Updates data — the first call changes the state, and subsequent identical calls keep the server in the same state.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both are considered &lt;strong&gt;idempotent&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;However, &lt;strong&gt;POST&lt;/strong&gt; is not idempotent by default. Each call to a POST endpoint typically creates a new entity — so if you retry the same request, you end up with duplicates.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Make Our Operation Idempotent
&lt;/h2&gt;

&lt;p&gt;The simplest way to make a non-idempotent API idempotent is to keep track of each request’s unique identifier — typically passed as a header like &lt;code&gt;X-Idempotency-Key&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;When the same key is received again, instead of re-executing the operation, we simply return the cached response stored from the first successful execution.&lt;/p&gt;
&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The code for this project can be found in this repository:&lt;br&gt;
&lt;a href="https://github.com/hassan314159/simply-order" rel="noopener noreferrer"&gt;https://github.com/hassan314159/simply-order&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this repository is continuously updated, the code specific to this lesson can be found in the &lt;em&gt;add_inventory_idempotency&lt;/em&gt;. branch. Start with:&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout add_inventory_idempotency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Order Service Changes
&lt;/h3&gt;

&lt;p&gt;The &lt;strong&gt;Order Service&lt;/strong&gt;, through the Saga workflow, calls the &lt;strong&gt;Inventory Service&lt;/strong&gt; to reserve items.&lt;br&gt;&lt;br&gt;
To properly track these requests, we need to attach an &lt;strong&gt;idempotency key&lt;/strong&gt; header that is unique for each order.&lt;br&gt;&lt;br&gt;
This way, if the same &lt;strong&gt;RESERVE&lt;/strong&gt; operation is executed multiple times, the Inventory Service can detect it and avoid processing it again.  &lt;/p&gt;

&lt;p&gt;We found that the existing &lt;code&gt;sagaId&lt;/code&gt;, which is unique for each Saga transaction, is a great candidate for our idempotency key.&lt;br&gt;&lt;br&gt;
Here’s how we add it inside:&lt;br&gt;&lt;br&gt;
&lt;code&gt;dev.simplyoder.order.temporal.activities.OrderActivitiesImpl.java&lt;/code&gt;&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="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Idempotency-Key"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sagaId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;":reserve"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The suffix &lt;code&gt;:reserve&lt;/code&gt; helps distinguish between different operations (like reserve and release) under the same Saga context&lt;/p&gt;

&lt;h3&gt;
  
  
  Inventory Service Change
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Idempotency Entity
&lt;/h4&gt;

&lt;p&gt;To implement idempotency in the &lt;strong&gt;Inventory Service&lt;/strong&gt;, we need a datastore to keep track of reserve order requests and their corresponding responses.&lt;/p&gt;

&lt;p&gt;But what do we actually mean by &lt;em&gt;response&lt;/em&gt;?&lt;br&gt;
In an HTTP call, the response mainly consists of two parts:&lt;/p&gt;

&lt;p&gt;The two main components for https responses are&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Status&lt;/strong&gt; – Indicates the outcome of the operation (e.g., 200 for success, 409 for conflict).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Payload&lt;/strong&gt; – The body returned from the service. In our case, it contains the &lt;code&gt;reservationId&lt;/code&gt; generated when the inventory was first reserved.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;We also keep track of some important fields such as the &lt;code&gt;aggregateId&lt;/code&gt;, which in our case represents the &lt;code&gt;orderId&lt;/code&gt;, along with the actual &lt;strong&gt;idempotency key&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;To make the record easily traceable and unique, we generate a &lt;strong&gt;deterministic UUID&lt;/strong&gt; (a UUID derived from the idempotency key and operation).&lt;br&gt;&lt;br&gt;
This ensures that every combination of key and operation always produces the same UUID — which helps us quickly detect duplicate requests without reprocessing them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s the entity that represents this data:&lt;br&gt;
&lt;code&gt;dev.simplyoder.inventory.persistence.IdempotencyEntity&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Id
private UUID key;
private String aggregateId;
private Integer httpStatus;
@Column(length = 4000)
private String jsonResponse;

private Instant createdAt;
private Instant updatedAt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Domain Entities
&lt;/h4&gt;

&lt;p&gt;At this stage, we haven’t fully implemented the persistence layer for the &lt;strong&gt;Inventory Service&lt;/strong&gt;, but we already have two main domain entities that define its core logic:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;InventoryItem&lt;/strong&gt; – Stores information about items in stock, including the item SKU, name, description, and the number of units currently available (&lt;code&gt;inStockQty&lt;/code&gt;).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ReservationHold&lt;/strong&gt; – Keeps track of item reservations so we can confirm or release them later, without directly modifying the main inventory table.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are the two entities:&lt;/p&gt;

&lt;h5&gt;
  
  
  InventoryItem
&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;dev.simplyoder.inventory.persistence.InventoryItemEntity&lt;/code&gt;&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;@Id&lt;/span&gt;
&lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;privte&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columnDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"text"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;inStockQty&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;reservedQty&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;updatedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h5&gt;
  
  
  ReservationHold
&lt;/h5&gt;

&lt;p&gt;&lt;code&gt;dev.simplyoder.inventory.persistence.ReservationHoldEntity&lt;/code&gt;&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;@Id&lt;/span&gt;
&lt;span class="nd"&gt;@GeneratedValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;GenerationType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;IDENTITY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;reservationId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"order_id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"idempotency_key"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Enumerated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EnumType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STRING&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// PENDING | CONFIRMED | CANCELLED&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Service Layer
&lt;/h4&gt;

&lt;p&gt;&lt;code&gt;dev.simplyoder.inventory.service.InventoryService&lt;/code&gt;&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="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ReservationsResponse&lt;/span&gt; &lt;span class="nf"&gt;reserve&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReservationsRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

&lt;span class="c1"&gt;// create a key based on provided idempotency key from the caller and operation&lt;/span&gt;
&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ReservationIds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fromKey&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;OP_RESERVE&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;Optional&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IdempotencyEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;idempotencyRow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;idempotencyRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//1 handle idempotency  &lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotencyRow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isPresent&lt;/span&gt;&lt;span class="o"&gt;()){&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;tryDeserialize&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idempotencyRow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orElseGet&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ReservationsResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;//2 Safe check: if domain already created reservation (previous crash)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(!&lt;/span&gt;&lt;span class="n"&gt;reservationHoldRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findByReservationId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;isEmpty&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ReservationsResponse&lt;/span&gt; &lt;span class="n"&gt;resp&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;ReservationsResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;saveIdempotency&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&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;serializeResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 3 Perform doman operation in a saperate transaction&lt;/span&gt;
    &lt;span class="nc"&gt;ReservationsResponse&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reserveDomainTransaction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// @Transactional&lt;/span&gt;
    &lt;span class="n"&gt;saveIdempotency&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OK&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;serializeResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// REQUIRES_NEW&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;InsufficientStockException&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;saveIdempotency&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;HttpStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CONFLICT&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;serializeResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;EMPTY_MAP&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;     &lt;span class="c1"&gt;// REQUIRES_NEW&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;ex&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;p&gt;The logic is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Check Idempotency&lt;/strong&gt; – Verify if a record already exists for the given idempotency key.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secondary Safeguard&lt;/strong&gt; – Handle the rare case where an idempotency record was created, but the actual item reservation never happened (for example, due to a crash right after saving the key).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Perform Reservation Logic&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Check the available stock and deduct the requested amount.
&lt;/li&gt;
&lt;li&gt;Create a &lt;code&gt;Reservation&lt;/code&gt; record so that the items can later be confirmed or released.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The idempotency handling and the business operation are executed in &lt;strong&gt;two separate transactions&lt;/strong&gt;, since they are logically independent.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The domain transaction logic is as follows:&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;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ReservationsResponse&lt;/span&gt; &lt;span class="nf"&gt;reserveDomainTransaction&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ReservationsRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;forEach&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;itemEntity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inventoryItemRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findBySku&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;itemEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInStockQty&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;itemEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getReservedQty&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InsufficientStockException&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Insufficient stock for "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;itemEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setReservedQty&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;itemEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getReservedQty&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;inventoryItemRepository&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="n"&gt;itemEntity&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;

    &lt;span class="c1"&gt;// persist the holds for later release-by-id&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rows&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;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReservationHoldEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rh&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ReservationHoldEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reserve&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;idempotencyKey&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rh&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;reservationHoldRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;saveAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ReservationsResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rid&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;h2&gt;
  
  
  Homework
&lt;/h2&gt;

&lt;p&gt;For now, we’ve secured our &lt;strong&gt;reserve&lt;/strong&gt; endpoint with idempotency.&lt;br&gt;&lt;br&gt;
However, the &lt;strong&gt;release&lt;/strong&gt; operation is still not idempotent.  &lt;/p&gt;

&lt;p&gt;The same approach can be applied here — simply reuse the existing &lt;code&gt;IdempotencyEntity&lt;/code&gt; and implement the same logic for the &lt;strong&gt;release&lt;/strong&gt; endpoint to ensure it behaves consistently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;We’ve now made our &lt;strong&gt;Inventory Service&lt;/strong&gt; more resilient by introducing &lt;strong&gt;idempotency&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
It’s a small change in code but a big improvement in reliability.  &lt;/p&gt;

&lt;p&gt;In distributed systems, retries aren’t optional — they’re part of survival.&lt;br&gt;&lt;br&gt;
By making our endpoints idempotent, we ensure that duplicated network calls or Saga retries don’t cause inconsistent states or double reservations.  &lt;/p&gt;

&lt;p&gt;Idempotency and the &lt;strong&gt;Outbox Pattern&lt;/strong&gt; (from the previous part) go hand in hand — one protects against duplicated operations, the other against lost events.&lt;br&gt;&lt;br&gt;
Together, they make your system predictable and safe to retry, which is exactly what you want in a distributed environment.&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>microservices</category>
      <category>api</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Simply Order (Part 5) — Hands-On: Building the Outbox Pattern for Reliable Event</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Tue, 21 Oct 2025 22:05:30 +0000</pubDate>
      <link>https://forem.com/hassan314159/simply-order-part-5-hands-on-building-the-outbox-pattern-for-reliable-event-60n</link>
      <guid>https://forem.com/hassan314159/simply-order-part-5-hands-on-building-the-outbox-pattern-for-reliable-event-60n</guid>
      <description>&lt;p&gt;This is the fifth article in our series, where we design a simple order solution for a hypothetical company called &lt;strong&gt;Simply Order&lt;/strong&gt;. The company expects high traffic and needs a resilient, scalable, and distributed order system.&lt;/p&gt;

&lt;p&gt;In the previous articles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/distributed-transactions-in-microservices-why-2pc-doesnt-fit-and-how-sagas-help-1lb"&gt;Simply Order (Part 1) Distributed Transactions in Microservices: Why 2PC Doesn’t Fit and How Sagas Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23"&gt;Simply Order (Part 2) — Designing and Implementing the Saga Workflow with Temporal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe"&gt;Simply Order (Part 3) — Linking It All Together: Connecting Services and Watching Temporal in Action&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-4-reliable-events-with-the-outbox-pattern-concepts-55ko"&gt;Simply Order (Part 4) — Reliable Events with the Outbox Pattern (Concepts)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We built the core services — Order, Payment, and Inventory — and discussed different approaches for handling distributed transactions across multiple services. Then, we designed and implemented the Saga workflow. Finally, we introduced the problem of dual-write consistency and how the Outbox Pattern can solve it.&lt;/p&gt;

&lt;p&gt;In this article, we’ll show how to implement the Outbox Pattern with a polling relay using PostgreSQL. Then, we’ll integrate this logic into our Order Service and see it in action.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code for this project can be found in this repository:&lt;br&gt;
&lt;a href="https://github.com/hassan314159/simply-order" rel="noopener noreferrer"&gt;https://github.com/hassan314159/simply-order&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this repository is continuously updated, the code specific to this lesson can be found in the &lt;em&gt;add_persistence_to_order_service&lt;/em&gt;. branch. Start with:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout add_persistence_to_order_service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Outbox Entity Definition
&lt;/h2&gt;

&lt;p&gt;Lets have a look about our &lt;code&gt;OutboxEntity&lt;/code&gt; definition. You can find the code in &lt;strong&gt;OrderService&lt;/strong&gt; under the package &lt;code&gt;dev.simplyoder.order.infra.outbox&lt;/code&gt;&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;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"outbox"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OutboxEntity&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"event_type"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;eventType&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"aggregate_id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;aggregateId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"payload"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columnDefinition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"jsonb"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@JdbcTypeCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SqlTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;JSON&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Enumerated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EnumType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STRING&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Status&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"available_at"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;availableAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"created_at"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updatable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt; &lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Version&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;Status&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="no"&gt;PENDING&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SENT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;FAILED&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Where &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;id&lt;/code&gt;: the ID of the outbox record.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;eventType&lt;/code&gt;: the type of event, e.g., &lt;strong&gt;OrderCreated&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aggregateId&lt;/code&gt;: the ID of the domain object, e.g., &lt;strong&gt;OrderId&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;payload&lt;/code&gt;: the event payload, e.g., the created order record&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;status&lt;/code&gt;: the status of the outbox record; one of:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PENDING&lt;/strong&gt;: new record to be polled by the relay&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SENT&lt;/strong&gt;: polled and executed successfully&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FAILED&lt;/strong&gt;: polled but execution failed (after a number of attempts)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;attempts&lt;/code&gt;: number of attempts before marking the record as &lt;strong&gt;FAILED&lt;/strong&gt;
&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;createdAt&lt;/code&gt;: the creation timestamp of the outbox record&lt;/li&gt;

&lt;li&gt;
&lt;code&gt;availableAt&lt;/code&gt;: when the record becomes eligible for execution. It starts as &lt;code&gt;createdAt&lt;/code&gt; (i.e., available immediately) and is updated on failure to support a backoff mechanism—delaying the next attempt by some time.&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Outbox Relay Logic
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Scheduled&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fixedDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;)&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;orderWorkFlowSchedular&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OutboxEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&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;lockNextBatch&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;now&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;batchSize&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 1&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OutboxEntity&lt;/span&gt; &lt;span class="n"&gt;ob&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// 2&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;workflowId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAggregateId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="nc"&gt;WorkflowOptions&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;WorkflowOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTaskQueue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-task-queue"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setWorkflowId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workflowId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setWorkflowIdReusePolicy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="nc"&gt;OrderWorkflow&lt;/span&gt; &lt;span class="n"&gt;wf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temporalClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newWorkflowStub&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderWorkflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPayload&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;markSent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 3&lt;/span&gt;

        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowServiceException&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// Workflow already exists with the same ID? Treat as success (idempotent start).&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isAlreadyStarted&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;// 4&lt;/span&gt;
                &lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;markSent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// backoff&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttempts&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reschedule&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextBackoff&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttempts&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;  &lt;span class="c1"&gt;// 5&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;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttempts&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fail&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// 6&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reschedule&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nextBackoff&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ob&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getAttempts&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;span class="o"&gt;}&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;saveAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&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;p&gt;The Outbox Relay is implemented as a simple scheduled task in our Order Service, running every &lt;em&gt;500 ms&lt;/em&gt;  using &lt;code&gt;@Scheduled(fixedDelay = 500)&lt;/code&gt;.&lt;br&gt;
It performs the following steps:&lt;br&gt;
1- Fetches and locks the next batch of outbox records using &lt;code&gt;outboxRepo.lockNextBatch&lt;/code&gt;&lt;br&gt;
2- Loops through each record and starts a new transaction or Temporal workflow using the &lt;em&gt;payload&lt;/em&gt; and &lt;em&gt;OrderId (aggregateId)&lt;/em&gt;.&lt;br&gt;
3- Updates the outbox record &lt;code&gt;status&lt;/code&gt; to SENT in case of success — &lt;code&gt;ob.markSent();&lt;/code&gt;.&lt;br&gt;
4- If a workflow has already been started for the same OrderId, it marks the outbox record as &lt;strong&gt;SENT&lt;/strong&gt; as well —  &lt;code&gt;if (isAlreadyStarted(e))&lt;/code&gt;&lt;br&gt;
5- Updates the &lt;code&gt;availableAt&lt;/code&gt; field in case of failure (but only if the number of attempts has not yet been exceeded).&lt;br&gt;
6- If the number of attempts exceeds the limit, it updates the &lt;code&gt;status&lt;/code&gt; to &lt;em&gt;FAILED&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;lockNextBatch&lt;/code&gt; in &lt;em&gt;OutboxRepository&lt;/em&gt; — fetches and locks the selected records to ensure that, if multiple service instances or relays are running, each outbox record is processed by only one relay.&lt;/p&gt;

&lt;p&gt;To support idempotency (i.e., preventing the same workflow from running twice for the same ID), we configured our workflow with: &lt;code&gt;.setWorkflowIdReusePolicy(WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE)&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;In our design, the payload contains the full Order record — including both &lt;code&gt;Order&lt;/code&gt; and &lt;code&gt;OrderItems&lt;/code&gt;. We chose this approach to make the relay self-contained and independent of the order business logic. In fact, the workflow and outbox code could be completely moved out of the &lt;strong&gt;Order Service&lt;/strong&gt; into a standalone service, but for simplicity, we kept it within the Order Service.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;
  
  
  Updated Order Logic
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Order Entity
&lt;/h3&gt;

&lt;p&gt;We use &lt;strong&gt;JPA&lt;/strong&gt; to persist order entities in our &lt;strong&gt;PostgreSQL&lt;/strong&gt; database.&lt;br&gt;
Our repositories are implemented using &lt;strong&gt;Spring Data&lt;/strong&gt;:&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;@Repository&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderRepository&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;JpaRepository&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderEntity&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;&amp;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;p&gt;This allows us to handle all basic CRUD operations without writing boilerplate code, while still being able to define custom queries when needed.&lt;/p&gt;

&lt;p&gt;Here is the &lt;code&gt;OrderEntity&lt;/code&gt; class.&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;@Entity&lt;/span&gt;
&lt;span class="nd"&gt;@Table&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderEntity&lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@Id&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Enumerated&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EnumType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;STRING&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OPEN&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;precision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ZERO&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@OneToMany&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mappedBy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"order"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cascade&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CascadeType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ALL&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orphanRemoval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@JsonManagedReference&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItemEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&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;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;OrderItemEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"created_at"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updatable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;OffsetDateTime&lt;/span&gt; &lt;span class="n"&gt;createdAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@Column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"updated_at"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nullable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;OffsetDateTime&lt;/span&gt; &lt;span class="n"&gt;updatedAt&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Order Service
&lt;/h3&gt;

&lt;p&gt;Right now, the Order Service is much simpler and focused purely on the order domain.&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;@Transactional&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;JsonProcessingException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;CreateOrderCommand&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderCommand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;OrderEntity&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OrderEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&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="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//1&lt;/span&gt;

    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;objectMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;writeValueAsString&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="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="nc"&gt;OutboxEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;pending&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"OrderCreated"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;//2&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderId&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;p&gt;The &lt;code&gt;OrderService&lt;/code&gt; logic consists of just two operations in a single transaction:&lt;br&gt;
1- Create the &lt;code&gt;Order&lt;/code&gt; entity&lt;br&gt;
2- Create the &lt;code&gt;Outbox&lt;/code&gt;order&lt;/p&gt;
&lt;h2&gt;
  
  
  Run Application
&lt;/h2&gt;

&lt;p&gt;As discussed in &lt;a href="https://dev.to/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe"&gt;Lesson 3&lt;/a&gt;, &lt;/p&gt;

&lt;p&gt;to run the application&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ./deploy/local
docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;please follow the same steps mentioned in Lesson 3 to create orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Home Work
&lt;/h2&gt;

&lt;p&gt;Try stopping the Temporal service and then create a new order.&lt;br&gt;
After restarting Temporal, you’ll notice that the order gets created and the distributed transaction continues automatically once Temporal becomes healthy again.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this lesson, we implemented the Outbox Pattern with a polling relay using PostgreSQL and integrated it into our Order Service.&lt;br&gt;
We saw how the relay reliably picks up pending events, triggers the corresponding Temporal workflows, and ensures eventual consistency across distributed services — even in cases of temporary failures or service downtime.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>architecture</category>
      <category>designpatterns</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Simply Order (Part 4) — Reliable Events with the Outbox Pattern (Concepts)</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Sun, 12 Oct 2025 08:18:45 +0000</pubDate>
      <link>https://forem.com/hassan314159/simply-order-part-4-reliable-events-with-the-outbox-pattern-concepts-55ko</link>
      <guid>https://forem.com/hassan314159/simply-order-part-4-reliable-events-with-the-outbox-pattern-concepts-55ko</guid>
      <description>&lt;p&gt;This is the fourth article in our series, where we design a simple order solution for a hypothetical company called &lt;strong&gt;Simply Order&lt;/strong&gt;. The company expects high traffic and needs a resilient, scalable, and distributed order system.&lt;/p&gt;

&lt;p&gt;In previous lectures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/distributed-transactions-in-microservices-why-2pc-doesnt-fit-and-how-sagas-help-1lb"&gt;Simply Order (Part 1) Distributed Transactions in Microservices: Why 2PC Doesn’t Fit and How Sagas Help&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23"&gt;Simply Order (Part 2) — Designing and Implementing the Saga Workflow with Temporal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe"&gt;Simply Order (Part 3) — Linking It All Together: Connecting Services and Watching Temporal in Action&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we built the core services — Order, Payment, and Inventory — and discussed the possible ways of implementing distributed transactions across multiple services. Then we designed and implemented our workflow.&lt;/p&gt;

&lt;p&gt;In this and the next lessons, we will add data persistence to our application. We’ll start with the &lt;strong&gt;Order Service&lt;/strong&gt;, using &lt;strong&gt;PostgreSQL&lt;/strong&gt; to persist our orders.&lt;/p&gt;

&lt;p&gt;But before diving into creating tables, let’s remind ourselves how the Order Service currently looks:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/hassan314159/simply-order/blob/master/order-service/src/main/java/dev/simplyoder/order/service/OrderService.java#L28C1-L50C6" rel="noopener noreferrer"&gt;&lt;code&gt;public UUID createOrder(CreateOrderRequest request)&lt;/code&gt;&lt;/a&gt;&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="kd"&gt;public&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
    &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ZERO&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&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;LinkedList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;multiply&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;())));&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&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="na"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="o"&gt;}&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="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;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OPEN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&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="nc"&gt;OrderWorkflow&lt;/span&gt; &lt;span class="n"&gt;wf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newWorkflowStub&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;OrderWorkflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;WorkflowOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTaskQueue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-task-queue"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setWorkflowId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// This is saga id&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="nc"&gt;WorkflowClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;wf:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderWorkflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&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="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderId&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;p&gt;The service creates an 'Order' object and then starts the orchestrated order transaction.&lt;br&gt;
We will now update the part responsible for creating the order to store it in the database instead of in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚠️ Dual-write inconsistency
&lt;/h2&gt;

&lt;p&gt;Because the database and Temporal broker are independent systems, the operation is not atomic.&lt;br&gt;
This can lead to inconsistent states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DB Ok, Broker fails:&lt;/strong&gt; We create an order in the database (status = OPEN), but no workflow transaction is started — other services remain unaware → stale order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DB fails, Broker OK:&lt;/strong&gt; The order is not persisted, but a workflow is started → a ghost order exists in the system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🤔 Using a 2PC (Two-Phase Commit) transaction could solve this, but it’s slow, complex, and impractical for microservices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outbox Pattern
&lt;/h2&gt;

&lt;p&gt;When we face such a problem, the Outbox pattern can be our savior.&lt;br&gt;
It follows a very simple idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When we create the order in the database, we also create another record in an outbox table corresponding to that order event.&lt;/li&gt;
&lt;li&gt;Both records are committed in one transaction — they either succeed or fail together, ensuring consistency.&lt;/li&gt;
&lt;li&gt;A separate relay process then polls the outbox table and starts the workflow independently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This guarantees that the distributed transaction will eventually be started, even if the broker was temporarily unavailable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outbox flavors
&lt;/h2&gt;

&lt;p&gt;There are two main ways to implement the Outbox pattern:&lt;/p&gt;

&lt;h3&gt;
  
  
  Outbox with polling Relay
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8o17lcgwwxfky59hfgy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp8o17lcgwwxfky59hfgy.png" alt="Outbox Relay" width="641" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Flow
&lt;/h4&gt;

&lt;p&gt;1- The application writes the business record (i.e., Order) and the outbox record in one transaction.&lt;br&gt;
 2- A poller/relay (scheduled job) scans the outbox table for new records.&lt;br&gt;
 3- The relay starts the workflow and marks the record as SENT.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Simple mechanism with no additional infrastructure layer.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Polling adds load to the database.&lt;/li&gt;
&lt;li&gt;You own the relay, so you must handle retries, backoff, and failures.&lt;/li&gt;
&lt;li&gt;The database must support transactions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Outbox with CDC/Log Tailing
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2z53lqkazumn4four7re.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2z53lqkazumn4four7re.png" alt="Outbox CDC" width="626" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;CDC&lt;/strong&gt; Change Data Capture: A method to detect row-level changes in a database (inserts, updates, deletes) and stream them to other systems in near real time — without touching application code. Often implemented by reading the database’s transaction log.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;transaction Log&lt;/strong&gt;: An append-only record of every database change (insert, update, delete) in exact order, allowing the database to replay, undo, replicate, or stream changes.&lt;/em&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Flow
&lt;/h4&gt;

&lt;p&gt;1- Application write business record i.e Order + Outbox record in DB in one Transaction&lt;br&gt;
 2- A CDC tool tails the DB’s transaction log and streams outbox inserts to a stream service i.e Kafka.&lt;br&gt;
 3- A consumer can listen to changes and start the workflow.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;No polling overhead on the DB.&lt;/li&gt;
&lt;li&gt;Very low latency and operationally robust once set up.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Requires CDC infrastructure (Kafka Connect, Debezium, connectors, monitoring, etc.).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Our Approach
&lt;/h2&gt;

&lt;p&gt;CDC is a great fit when you already have a streaming infrastructure (e.g., Kafka) or when you need near real-time event processing.&lt;br&gt;
But this is not our current case.&lt;/p&gt;

&lt;p&gt;So, we’ll implement Outbox with Polling Relay to handle this consistency problem between the database and the Temporal workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;In this lesson, we explored one of the most important reliability patterns in distributed systems — the &lt;strong&gt;Outbox pattern&lt;/strong&gt;.&lt;br&gt;
We started with the dual-write problem and saw why direct DB + broker writes can lead to inconsistent states.&lt;br&gt;
Then, we learned how the Outbox pattern guarantees atomicity and reliability by coupling data persistence with event publishing — either via a polling relay or CDC/log tailing approach.&lt;/p&gt;

&lt;p&gt;In our &lt;strong&gt;Simply Order&lt;/strong&gt; project, we’ll go with the polling relay implementation, since it’s simpler and requires no additional infrastructure.&lt;/p&gt;

&lt;p&gt;➡️ In the next lesson, we’ll dive into the actual implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create the outbox table&lt;/li&gt;
&lt;li&gt;Persist events along with orders in one transaction&lt;/li&gt;
&lt;li&gt;Build a simple polling relay to trigger the Temporal workflow reliably&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned — that’s where we’ll make reliability real. 💪&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>microservices</category>
      <category>architecture</category>
      <category>designpatterns</category>
    </item>
    <item>
      <title>Simply Order (Part 3) — Linking It All Together: Connecting Services and Watching Temporal in Action</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Sun, 28 Sep 2025 17:32:04 +0000</pubDate>
      <link>https://forem.com/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe</link>
      <guid>https://forem.com/hassan314159/simply-order-part-3-linking-it-all-together-connecting-services-and-watching-temporal-in-action-19oe</guid>
      <description>&lt;p&gt;This is the third article in our series, where we design a simple order solution for a hypothetical company called “Simply Order”. The company expects high traffic and needs a resilient, scalable, and distributed order system.&lt;/p&gt;

&lt;p&gt;In previous lecture &lt;a href="https://dev.to/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23"&gt;Designing and Implementing the Saga Workflow&lt;/a&gt;, we designed and implemented the Temporal workflow. In this lecture, we’ll build the skeleton for the three participating services, connect them together, and run the application using Docker Compose.&lt;/p&gt;

&lt;p&gt;We’ll start with in-memory data only. In the next lecture, we’ll add a persistence layer, discuss the challenges that come with it, and explore possible solutions and their trade-offs. So stay tuned—let’s dive into the code for our services:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The code for this project can be found in this repository:&lt;br&gt;
&lt;a href="https://github.com/hassan314159/simply-order" rel="noopener noreferrer"&gt;https://github.com/hassan314159/simply-order&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this repository is continuously updated, the code specific to this lesson can be found in the branch &lt;em&gt;connecting_and_running_app&lt;/em&gt;. Start with:&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git checkout connecting_and_running_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Order Service
&lt;/h2&gt;

&lt;p&gt;The Order Service is our entry point—the place where we create orders and start the workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controller
&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;@PostMapping&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;201&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;body&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;CreateOrderResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findOrderById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;getStatus&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{id}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findOrderById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findOrderById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt;
           &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notFound&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;build&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;p&gt;It exposes two endpoints: one to create an order and another to retrieve an order with its status.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service
&lt;/h3&gt;

&lt;p&gt;When a new order is created, the service builds an order object with status &lt;em&gt;OPEN&lt;/em&gt; and stores it in a Map that acts as our in-memory database. It then starts the workflow code we defined in the previous lecture, as shown below:&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="kd"&gt;public&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="nf"&gt;createOrder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
    &lt;span class="o"&gt;...&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="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;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OPEN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// simple map acts as in in-memory store&lt;/span&gt;
    &lt;span class="n"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&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="c1"&gt;// start oder creation saga ** WHEN INTRODUCE DB WILL BE UPDATED TO BE STARTED BY OUTBOX RELAY&lt;/span&gt;
    &lt;span class="nc"&gt;OrderWorkflow&lt;/span&gt; &lt;span class="n"&gt;wf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newWorkflowStub&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;OrderWorkflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nc"&gt;WorkflowOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTaskQueue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-task-queue"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setWorkflowId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// This is saga id&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;

    &lt;span class="nc"&gt;WorkflowClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;wf:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;placeOrder&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderWorkflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;from&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="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;orderId&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;h2&gt;
  
  
  Inventory Service
&lt;/h2&gt;

&lt;p&gt;The Inventory Service is very simple—it accepts a request and returns an &lt;code&gt;OK&lt;/code&gt; response so the Saga can continue.&lt;/p&gt;

&lt;p&gt;We added a temporary condition: if the number of items is greater than 3, the service responds with &lt;code&gt;209&lt;/code&gt;, indicating that the inventory does not have enough items. This allows our Saga to roll back, i.e., apply the compensation operations.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;But what if the service is unreachable? Will the Saga fail on its request?&lt;br&gt;
Short answer: No. This depends on the activity retry configuration we defined in the previous lecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’ve also provided an endpoint that Temporal workers will call to apply the compensation. &lt;code&gt;/reservations/{reservationsId}/release&lt;/code&gt;&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;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/reservations"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ReservationsResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;ReservationsRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;size&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Items reserved successfully"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&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;ReservationsResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Items could not be reserved"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;209&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&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;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/reservations/{reservationsId}/release"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;reservationsId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Items released for reservation: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reservationsId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;noContent&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;build&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;h2&gt;
  
  
  Payment Service
&lt;/h2&gt;

&lt;p&gt;Like the Inventory Service, the Payment Service authorizes requests with trivial logic: if the order cost is less than &lt;em&gt;500&lt;/em&gt;, it authorizes the payment; if it’s 500 or greater, it rejects it and returns &lt;code&gt;209&lt;/code&gt; as a rollback signal so the Saga can apply compensation.&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;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/authorize"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PaymentAuthorizeResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestBody&lt;/span&gt; &lt;span class="nc"&gt;PaymentAuthorizeRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;compareTo&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BigDecimal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;){&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Payment processed successfully"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ok&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;PaymentAuthorizeResponse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Payment Failed"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;209&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&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;span class="nd"&gt;@PostMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/{paymentId}/void"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@PathVariable&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;authId&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;LOG&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Payment refunded: {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;authId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ResponseEntity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;noContent&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;build&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;h2&gt;
  
  
  Run the application
&lt;/h2&gt;

&lt;p&gt;We’ve provided a &lt;strong&gt;Docker Compose&lt;/strong&gt; setup that builds and starts the services, Temporal Server, and Temporal UI. Temporal UI is a powerful tool that allows us to trace the progress of our workflows.&lt;/p&gt;

&lt;p&gt;If Docker Compose isn’t installed in your environment, install it first. Then, from the repository’s root directory, navigate to the folder containing the Docker Compose file and run the services:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ./deploy/local
docker compose up
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check the running services, just run &lt;code&gt;docker-compose ps&lt;/code&gt;. You should see something like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8do7awhxyv0rv9utlpia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8do7awhxyv0rv9utlpia.png" alt="services staus" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  First Order Creation
&lt;/h2&gt;

&lt;p&gt;One of the best auxiliary features of Temporal is the Temporal UI — an interface that lets us trace our workflows.&lt;/p&gt;

&lt;p&gt;Let’s open this URL in our browser: &lt;code&gt;localhost:8080&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1qm7hnloomwcyul23dkj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1qm7hnloomwcyul23dkj.png" alt="Temporal UI, Zero Workflow" width="626" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For now, no workflows have been created.&lt;br&gt;
Let’s start a transaction and see how we can trace it.&lt;/p&gt;

&lt;p&gt;Let’s create our first order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'localhost:8081/orders'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "customerId": "123e4567-e89b-12d3-a456-426614174000",
    "items": [
      {
        "sku": "ABC-001",
        "qty": 1,
        "price": 19.99
      },
      {
        "sku": "XYZ-123",
        "qty": 1,
        "price": 12.95
      }
    ]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"orderId"&lt;/span&gt;: &lt;span class="s2"&gt;"71ed318d-10ea-4371-af34-1581dc315dde"&lt;/span&gt;,
    &lt;span class="s2"&gt;"status"&lt;/span&gt;: &lt;span class="s2"&gt;"OPEN"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We’ve created an order, and its status is &lt;code&gt;OPEN&lt;/code&gt;. Let’s check its status for now:&lt;/p&gt;

&lt;p&gt;Let’s call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'localhost:8081/orders/71ed318d-10ea-4371-af34-1581dc315dde'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;71ed318d-10ea-4371-af34-1581dc315dde&lt;/code&gt; is the order ID you got when creating the order. You should use the UUID generated by your own code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"71ed318d-10ea-4371-af34-1581dc315dde"&lt;/span&gt;,
    &lt;span class="s2"&gt;"customerId"&lt;/span&gt;: &lt;span class="s2"&gt;"123e4567-e89b-12d3-a456-426614174000"&lt;/span&gt;,
    &lt;span class="s2"&gt;"status"&lt;/span&gt;: &lt;span class="s2"&gt;"COMPLETED"&lt;/span&gt;,
    &lt;span class="s2"&gt;"total"&lt;/span&gt;: 32.94,
    &lt;span class="s2"&gt;"items"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sku"&lt;/span&gt;: &lt;span class="s2"&gt;"ABC-001"&lt;/span&gt;,
            &lt;span class="s2"&gt;"qty"&lt;/span&gt;: 1,
            &lt;span class="s2"&gt;"price"&lt;/span&gt;: 19.99
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sku"&lt;/span&gt;: &lt;span class="s2"&gt;"XYZ-123"&lt;/span&gt;,
            &lt;span class="s2"&gt;"qty"&lt;/span&gt;: 1,
            &lt;span class="s2"&gt;"price"&lt;/span&gt;: 12.95
        &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;p&gt;We can see that the status is Completed — but what happened?&lt;br&gt;
This is where the Temporal UI comes in. Let’s open its URL again &lt;code&gt;localhost:8080&lt;/code&gt; and click on the workflow.&lt;br&gt;
You can identify it as &lt;code&gt;order-&amp;lt;UUID&amp;gt;&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Now we can see the timeline of our order’s workflow, as shown here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybw7tn8r0c3qa2adawm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fybw7tn8r0c3qa2adawm7.png" alt="Order Creation Workflow" width="800" height="332"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  One Service is down
&lt;/h2&gt;

&lt;p&gt;Let’s assume one service is down — for example, the payment service. How can Temporal handle this?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In our branch, we’ve updated the retry interval of the workflow activity to 200 seconds so we can debug the workflow while the service is down.&lt;/em&gt;&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RetryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setMaximumAttempts&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setBackoffCoefficient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setInitialInterval&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To simulate a service being down, just stop the payment service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker compose stop payment-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, let’s create another order and check its status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'localhost:8081/orders'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "customerId": "123e4567-e89b-12d3-a456-426614174000",
    "items": [
      {
        "sku": "ABC-001",
        "qty": 1,
        "price": 19.99
      },
      {
        "sku": "XYZ-123",
        "qty": 1,
        "price": 12.95
      }
    ]
  }'&lt;/span&gt;

Response: 
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"orderId"&lt;/span&gt;: &lt;span class="s2"&gt;"9cd35ac0-6973-4881-b68d-a37dbb1a2bff"&lt;/span&gt;,
    &lt;span class="s2"&gt;"status"&lt;/span&gt;: &lt;span class="s2"&gt;"OPEN"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s check the status of the order right now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'localhost:8081/orders/9cd35ac0-6973-4881-b68d-a37dbb1a2bff'&lt;/span&gt;

Response
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"9cd35ac0-6973-4881-b68d-a37dbb1a2bff"&lt;/span&gt;,
    &lt;span class="s2"&gt;"customerId"&lt;/span&gt;: &lt;span class="s2"&gt;"123e4567-e89b-12d3-a456-426614174000"&lt;/span&gt;,
    &lt;span class="s2"&gt;"status"&lt;/span&gt;: &lt;span class="s2"&gt;"INVENTORY_RESERVED"&lt;/span&gt;,
    &lt;span class="s2"&gt;"total"&lt;/span&gt;: 32.94,
    &lt;span class="s2"&gt;"items"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sku"&lt;/span&gt;: &lt;span class="s2"&gt;"ABC-001"&lt;/span&gt;,
            &lt;span class="s2"&gt;"qty"&lt;/span&gt;: 1,
            &lt;span class="s2"&gt;"price"&lt;/span&gt;: 19.99
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sku"&lt;/span&gt;: &lt;span class="s2"&gt;"XYZ-123"&lt;/span&gt;,
            &lt;span class="s2"&gt;"qty"&lt;/span&gt;: 1,
            &lt;span class="s2"&gt;"price"&lt;/span&gt;: 12.95
        &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;p&gt;We can see that the status of the order is &lt;code&gt;INVENTORY_RESERVED&lt;/code&gt;, which is expected. Since the payment service is down, Temporal activities are retrying calls to the payment service. According to our configuration, if the service becomes healthy again before 5 retries, our workflow can continue.&lt;/p&gt;

&lt;p&gt;If we check the Temporal UI, we’ll see its status as &lt;code&gt;Running&lt;/code&gt;:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flw6r1torzdu5h63kpvxo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flw6r1torzdu5h63kpvxo.png" alt="Running Workflow" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s start the service again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker compose start payment-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s check the status.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'localhost:8081/orders/9cd35ac0-6973-4881-b68d-a37dbb1a2bff'&lt;/span&gt;

Response
&lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"id"&lt;/span&gt;: &lt;span class="s2"&gt;"9cd35ac0-6973-4881-b68d-a37dbb1a2bff"&lt;/span&gt;,
    &lt;span class="s2"&gt;"customerId"&lt;/span&gt;: &lt;span class="s2"&gt;"123e4567-e89b-12d3-a456-426614174000"&lt;/span&gt;,
    &lt;span class="s2"&gt;"status"&lt;/span&gt;: &lt;span class="s2"&gt;"COMPLETED"&lt;/span&gt;,
    &lt;span class="s2"&gt;"total"&lt;/span&gt;: 32.94,
    &lt;span class="s2"&gt;"items"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sku"&lt;/span&gt;: &lt;span class="s2"&gt;"ABC-001"&lt;/span&gt;,
            &lt;span class="s2"&gt;"qty"&lt;/span&gt;: 1,
            &lt;span class="s2"&gt;"price"&lt;/span&gt;: 19.99
        &lt;span class="o"&gt;}&lt;/span&gt;,
        &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="s2"&gt;"sku"&lt;/span&gt;: &lt;span class="s2"&gt;"XYZ-123"&lt;/span&gt;,
            &lt;span class="s2"&gt;"qty"&lt;/span&gt;: 1,
            &lt;span class="s2"&gt;"price"&lt;/span&gt;: 12.95
        &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;p&gt;now it is &lt;code&gt;COMPLETED&lt;/code&gt; — whoa! 🎉&lt;/p&gt;

&lt;p&gt;To trace the timeline of what happened, we can check the Temporal UI:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7b8rll9z2og24vvd1q85.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7b8rll9z2og24vvd1q85.png" alt="Temporal History" width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From the timeline above, we can see when the service was down (light green) and when it became healthy again and succeeded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Home Work
&lt;/h2&gt;

&lt;p&gt;Try stopping the payment service and creating an order, but this time wait longer — until the order workflow fails — and then check the status…&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;In this lesson, we continued our example by running the app and exploring how Temporal helps us trace and recover workflows — even when a service goes down. We saw how the Temporal UI gives us full visibility into the workflow timeline and retries.&lt;/p&gt;

&lt;p&gt;In the next lesson, we’ll take our example one step further and add persistence, so our workflows can survive restarts and keep their state safely. 🚀&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>microservices</category>
      <category>distributedsystems</category>
      <category>temporal</category>
    </item>
    <item>
      <title>Simply Order (Part 2) — Designing and Implementing the Saga Workflow with Temporal</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Thu, 04 Sep 2025 18:09:40 +0000</pubDate>
      <link>https://forem.com/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23</link>
      <guid>https://forem.com/hassan314159/simply-order-part-2-designing-and-implementing-the-saga-workflow-with-temporal-3o23</guid>
      <description>&lt;p&gt;This is the second lesson in our series. In the first lesson, we defined the problem of distributed microservices in our hypothetical company &lt;strong&gt;Simply Order&lt;/strong&gt;. We explored possible solutions for handling distributed transactions in microservices, and discussed why &lt;strong&gt;Two-Phase Commit (2PC)&lt;/strong&gt; is not suitable for microservices and why the &lt;strong&gt;Saga pattern&lt;/strong&gt; can solve these problems.&lt;/p&gt;

&lt;p&gt;In this lesson, we move from concepts to implementation. We will design and implement the first version of our Saga workflow using &lt;strong&gt;Temporal&lt;/strong&gt; as the orchestrator.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we code: How to think about a saga?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Think about the business transaction (i.e. Place Order)
&lt;/h3&gt;

&lt;p&gt;The transaction will be as follows: &lt;em&gt;create order → reserve inventory → authorize payment → completed&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Draw the state machine of our transaction
&lt;/h3&gt;

&lt;p&gt;To be able to implement the transaction, we need to draw the state machine — i.e. all the states that our entity will go through.&lt;/p&gt;

&lt;p&gt;In our case, we can think of an order with these states:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
OPEN-&amp;gt; PENDING -&amp;gt; INVENTORY_RESERVED -&amp;gt; PAYMENT_AUTHORIZED -&amp;gt; COMPLETED&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
In case of errors, the status will be one of these:&lt;br&gt;
&lt;code&gt;&lt;br&gt;
INVENTORY_FAILED, PAYMENT_FAILED, FAILED_UNKNOWN&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  3) Why Orchestration Saga?
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ By default, we should consider Choreography because it is simpler: services just emit and listen to events. This works well for small, linear flows with few participants (see Chris Richardson, Microservices Patterns, and Sam Newman, Building Microservices).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But as soon as the flow becomes longer, more complex, or involves branching, choreography can easily turn into a &lt;em&gt;spaghetti of events&lt;/em&gt;, making it hard to trace and evolve the process.&lt;/p&gt;

&lt;p&gt;In the initial phase of our order service — which is relatively simple — choreography could be a good choice. But with our planned extension of the order service (as we’ll see in the next lessons), we opt for &lt;em&gt;Orchestration Saga&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We could implement our own saga orchestrator, but then we would need to handle workflow state, communication between the orchestrator and microservices, reliability, non-blocking execution, and visibility into workflow progress. All of this distracts us from focusing on business logic, which is what we as developers should prioritize.&lt;/p&gt;

&lt;p&gt;Here is where Temporal comes in as a solution…&lt;/p&gt;
&lt;h3&gt;
  
  
  What is Temporal
&lt;/h3&gt;

&lt;p&gt;In a nutshell, &lt;strong&gt;Temporal&lt;/strong&gt; is an open-source workflow orchestration platform that lets you write reliable, long-running processes in code. It handles retries, state, timeouts, and compensations automatically, so you can focus on business logic instead of building reliability infrastructure.&lt;/p&gt;

&lt;p&gt;👉 Note: Temporal is not limited to sagas — it’s a general-purpose workflow platform. Saga orchestration is just one of many patterns you can implement with it.&lt;/p&gt;

&lt;p&gt;In this lesson, we will focus on how to configure Temporal in our code and explore its core components.&lt;/p&gt;
&lt;h2&gt;
  
  
  Dive to Implmentation
&lt;/h2&gt;

&lt;p&gt;The code for this project can be found in this repository:&lt;br&gt;
&lt;a href="https://github.com/hassan314159/simply-order" rel="noopener noreferrer"&gt;https://github.com/hassan314159/simply-order&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Since this repository is continuously updated, the code specific to this lesson can be found in the branch &lt;code&gt;initial_order_saga&lt;/code&gt;. Start with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git checkout initial_order_saga
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Order Workflow definition
&lt;/h3&gt;

&lt;p&gt;Let’s jump to the implementation of our Saga workflow in &lt;code&gt;OrderWorkflowImpl.java&lt;/code&gt;.&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="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;Input&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sagaId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInfo&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getWorkflowId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="nc"&gt;Saga&lt;/span&gt; &lt;span class="n"&gt;saga&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;Saga&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;Saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setContinueWithError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// collect/continue if a compensation fails&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// 1) Update status of Order to PENDING&lt;/span&gt;
            &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PENDING&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// 2) reserve the inventory (calling inventory service)&lt;/span&gt;
            &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;reservationId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;reserveInventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;sagaId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;request&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addCompensation&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;releaseInventoryIfAny&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;reservationId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

            &lt;span class="c1"&gt;// 3) Update status of Order to INVENTORY_RESERVED&lt;/span&gt;
            &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;INVENTORY_RESERVED&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// 4) authorize the payment (calling payment service)&lt;/span&gt;
            &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;authId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authorizePayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;sagaId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addCompensation&lt;/span&gt;&lt;span class="o"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;voidPaymentIfAny&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;authId&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

            &lt;span class="c1"&gt;// 5) Update status of Order to PAYMENT_AUTHORIZED&lt;/span&gt;
            &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;PAYMENT_AUTHORIZED&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="c1"&gt;// 6) Complete our saga by updating Order Status to COMPLETED&lt;/span&gt;
            &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;COMPLETED&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;saga&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;compensate&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;     &lt;span class="c1"&gt;// runs: voidPayment → releaseInventory&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;act&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;updateOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;classifyError&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// precise status&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&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;p&gt;One of the great features of Temporal is the &lt;strong&gt;separation of concerns&lt;/strong&gt;. In this class, we define our saga flow independently of how the steps are implemented:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update the order status to &lt;code&gt;PENDING&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Call the Inventory service to reserve inventory, and register a &lt;em&gt;compensation&lt;/em&gt; step in case an error happens.&lt;/li&gt;
&lt;li&gt;Update the order status to &lt;code&gt;INVENTORY_RESERVED&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Call the Payment service to authorize the payment, and again register a &lt;em&gt;compensation&lt;/em&gt; step.&lt;/li&gt;
&lt;li&gt;Update the order status to &lt;code&gt;PAYMENT_AUTHORIZED&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Finally, complete the saga by updating the order status to &lt;code&gt;COMPLETED&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Defining the workflow steps and compensation logic is straightforward.&lt;br&gt;
We define the steps in the required business order. If any step needs compensation, we register the compensation after the step succeeds, so that if an error occurs later, compensation runs only for the steps that actually completed before the exception point.&lt;/p&gt;

&lt;p&gt;Now let’s look at a snippet of the &lt;code&gt;reserveInventory&lt;/code&gt; implementation from &lt;code&gt;OrderActivitiesImpl.java&lt;/code&gt;.&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;req&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;Req&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sku&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;())).&lt;/span&gt;&lt;span class="na"&gt;toList&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;headers&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;HttpHeaders&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Idempotency-Key"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sagaId&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;":reserve"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;exchange&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inventoryBase&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"/inventory/reservations"&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;HttpMethod&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;POST&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;HttpEntity&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;Res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getStatusCode&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="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;209&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nc"&gt;ApplicationFailure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newNonRetryableFailure&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"InventoryNotSufficient"&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;p&gt;The code may look like it’s blocking — just a synchronous HTTP call to the Inventory service.&lt;br&gt;
However, behind the scenes, each activity (&lt;code&gt;act.&lt;/code&gt;) is executed by a &lt;em&gt;Temporal Worker&lt;/em&gt;, which is a process that runs in a separate worker threads. This means it’s non-blocking from the workflow’s perspective.&lt;/p&gt;

&lt;p&gt;We can also define timeout and retry behavior, as shown in our constructor.&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="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RetryOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setMaximumAttempts&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setBackoffCoefficient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;setInitialInterval&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofSeconds&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;opts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ActivityOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setStartToCloseTimeout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofMinutes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)).&lt;/span&gt;&lt;span class="na"&gt;setRetryOptions&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we defined a single config for all activities, but in practice, each activity type can have its own configuration.&lt;/p&gt;

&lt;p&gt;For example, in this lesson we set a timeout of 2 minutes. In reality, Temporal allows activities to run indefinitely with automatic retries until they succeed — unless you explicitly configure a timeout or retry policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick Overview of Temporal Core Components
&lt;/h3&gt;

&lt;p&gt;Before moving on, let’s quickly recap the core components of Temporal (we’ll dive deeper into how they interact in a following lesson):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Workflow code: Your orchestration logic (deterministic, no external I/O).&lt;/li&gt;
&lt;li&gt;Activity code: Your side effects (call services/DBs/APIs).&lt;/li&gt;
&lt;li&gt;Worker: The process that runs workflow code and activity code. It polls a Task Queue.&lt;/li&gt;
&lt;li&gt;Temporal Server: Stores the event history, hands out tasks, tracks retries/timeouts.&lt;/li&gt;
&lt;li&gt;Task Queue: A named queue that decouples the server from workers.&lt;/li&gt;
&lt;li&gt;Client: Starts workflows, sends Signals, reads Queries.&lt;/li&gt;
&lt;li&gt;Persistence: DB/storage where the server saves workflow history/state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Continue Implementation...
&lt;/h3&gt;

&lt;p&gt;Let’s go one step back and see how we can set up our project with Temporal and define its core components.&lt;/p&gt;

&lt;p&gt;First, we need to import the Temporal SDK dependency:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.temporal&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;temporal-sdk&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${temporal.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Then, let’s define the Client and Worker beans. You can find this in &lt;code&gt;TemporalConfig.java&lt;/code&gt;.&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;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;WorkflowServiceStubs&lt;/span&gt; &lt;span class="nf"&gt;service&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@Value&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"${app.temporal.address}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;WorkflowServiceStubs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newServiceStubs&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;temporal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;serviceclient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WorkflowServiceStubsOptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newBuilder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setTarget&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;address&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;WorkflowClient&lt;/span&gt; &lt;span class="nf"&gt;workflowClient&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowServiceStubs&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;WorkflowClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;WorkerFactory&lt;/span&gt; &lt;span class="nf"&gt;workerFactory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkflowClient&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;WorkerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Bean&lt;/span&gt;
    &lt;span class="nc"&gt;Worker&lt;/span&gt; &lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WorkerFactory&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;OrderActivities&lt;/span&gt; &lt;span class="n"&gt;activities&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Worker&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newWorker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"order-task-queue"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerWorkflowImplementationTypes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;OrderWorkflowImpl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerActivitiesImplementations&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;activities&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;w&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;p&gt;We’ve seen the workflow code, but how does our app identify it as a Temporal Workflow?&lt;br&gt;
Temporal provides two annotations: &lt;code&gt;@WorkflowInterface&lt;/code&gt; and &lt;code&gt;@WorkflowMethod&lt;/code&gt;.&lt;br&gt;
We already saw the implementation of the &lt;code&gt;placeOrder&lt;/code&gt; method.&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;@WorkflowInterface&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderWorkflow&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Input&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;customerId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;Input&lt;/span&gt; &lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="o"&gt;(&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="nc"&gt;CreateOrderRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Input&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="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCustomerId&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;getTotal&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;req&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;span class="nd"&gt;@WorkflowMethod&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;Input&lt;/span&gt; &lt;span class="n"&gt;input&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;p&gt;Lastly, Temporal also needs to know the interface that defines the activities, so that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;Worker&lt;/strong&gt; can register those methods as activities.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Server&lt;/strong&gt; can schedule activity tasks and match them to the correct code running in the Worker.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and this is done simple by annotate the interface with &lt;code&gt;@ActivityInterface&lt;/code&gt; as in OrderActivities.java&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;@ActivityInterface&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;OrderActivities&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;updateOrderStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Status&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="nf"&gt;reserveInventory&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sagaId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;CreateOrderRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="nf"&gt;authorizePayment&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;sagaId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;BigDecimal&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;voidPaymentIfAny&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;paymentId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;releaseInventoryIfAny&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;UUID&lt;/span&gt; &lt;span class="n"&gt;reservationId&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;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;rapping up&lt;/p&gt;

&lt;p&gt;In this lesson, we turned the theory of Sagas into a working Order Workflow with Temporal — defining states, compensations, and error handling. This is just our first step, and there’s more to come as we add cancellation, shipment, and other real-world features.&lt;/p&gt;

&lt;p&gt;👉 Follow along for the next parts of the series, and feel free to drop your questions or share your own Saga experiences in the comments — I’d love to hear your thoughts!&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>temporal</category>
      <category>saga</category>
      <category>distributedtransaction</category>
    </item>
    <item>
      <title>Simply Order (Part 1) Distributed Transactions in Microservices: Why 2PC Doesn’t Fit and How Sagas Help</title>
      <dc:creator>Mohamed Hassan</dc:creator>
      <pubDate>Wed, 27 Aug 2025 18:40:25 +0000</pubDate>
      <link>https://forem.com/hassan314159/distributed-transactions-in-microservices-why-2pc-doesnt-fit-and-how-sagas-help-1lb</link>
      <guid>https://forem.com/hassan314159/distributed-transactions-in-microservices-why-2pc-doesnt-fit-and-how-sagas-help-1lb</guid>
      <description>&lt;p&gt;This is the first article in a series that walks through a simple order solution for a hypothetical company called “Simply Order”. The company expects high traffic and needs to design a resilient, scalable, and distributed Order system.&lt;/p&gt;

&lt;p&gt;We’ll go step by step, building the system and discussing the challenges along the way, while exploring the trade-offs between possible solutions.&lt;/p&gt;

&lt;p&gt;In this first article, we’ll describe the problem and review different approaches — with their pros and cons — before choosing our recommended path. In the upcoming articles, we’ll implement the solution, share code examples, and highlight best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The company chose a microservice architecture to build their system. They started with an Order microservice, but soon realized it was becoming a bottleneck.&lt;/p&gt;

&lt;p&gt;This service has to handle multiple responsibilities:&lt;br&gt;
Accept orders and store them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Communicate with the Inventory Service to check product availability.&lt;/li&gt;
&lt;li&gt;Communicate with the Payment Service to authenticate and process user payments.&lt;/li&gt;
&lt;li&gt;Communicate with the Fulfillment Service to start shipping and logistics.&lt;/li&gt;
&lt;li&gt;Finally, notify users and other interested participants through different channels — e.g., messages, emails, and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These multiple operations should be consistent and ideally treated as a single unit of work. But here’s the problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each microservice has its own DB → can’t use a single DB transaction across them. &lt;/li&gt;
&lt;li&gt;One service may commit successfully, while others fail. This means one service may commit successfully, while others fail.&lt;/li&gt;
&lt;li&gt;Some services may even use NoSQL databases, which don’t support traditional transactions at all.&lt;/li&gt;
&lt;li&gt;And to make it worse, some microservices might be unreachable at the time of the operation.&lt;/li&gt;
&lt;li&gt;So the big question is: how do we manage these operations and keep the system consistent in the chaotic world of microservices?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solutions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two-Phase Commit (2PC)
&lt;/h3&gt;

&lt;p&gt;A straightforward solution that often comes to mind is &lt;strong&gt;Two-Phase Commit (2PC)&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
Some relational databases and drivers support it, making it possible to coordinate a distributed transaction.  &lt;/p&gt;

&lt;p&gt;Here’s how it works:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;coordinator&lt;/strong&gt; talks to the participants (other microservices or their databases) and asks them to &lt;em&gt;prepare&lt;/em&gt; their transaction.
&lt;/li&gt;
&lt;li&gt;If all participants reply with &lt;em&gt;yes (prepared)&lt;/em&gt;, the coordinator sends a &lt;strong&gt;commit&lt;/strong&gt; message to everyone.
&lt;/li&gt;
&lt;li&gt;If one or more reply with &lt;em&gt;no&lt;/em&gt; (or fail to respond), the coordinator sends a &lt;strong&gt;rollback&lt;/strong&gt; message to all participants. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvut0f5j1l0ndmix2vd0z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvut0f5j1l0ndmix2vd0z.png" alt="Two-Phase Commit Sequence Diagram" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros &amp;amp; Cons
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Pros
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Guarantees atomicity across multiple services.&lt;/li&gt;
&lt;li&gt;Conceptually simple — feels like a “distributed version” of a database transaction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Cons
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Blocking: the prepare phase locks database resources until the final commit/rollback, which hurts scalability. Also, if any participant is down or unresponsive, the whole transaction may block and hold locks.&lt;/li&gt;
&lt;li&gt;Single point of failure: if the coordinator crashes, participants may stay locked indefinitely.&lt;/li&gt;
&lt;li&gt;Limited Support: Not all databases or drivers support XA/2PC transactions (especially many NoSQL or cloud-native DBs).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Because of these limitations, 2PC is generally not suitable for microservices in a cloud-native environment.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Saga
&lt;/h3&gt;

&lt;p&gt;Saga splits one big transaction that involves multiple services into a sequence of local transactions owned by each service. If one service fails to commit its transaction, other services get informed about that failure, and instead of rollback (services do not block, they already committed their changes), they perform a compensating transaction. i.e. if the Inventory service deducted/reserved certain items, it will create another transaction to add/free the deducted items.&lt;/p&gt;

&lt;p&gt;But how can each service be informed about other services?&lt;/p&gt;

&lt;p&gt;There are 2 types of Saga: &lt;strong&gt;Choreography&lt;/strong&gt; and &lt;strong&gt;Orchestration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are 2 types of Saga: &lt;strong&gt;Choreography&lt;/strong&gt; and &lt;strong&gt;Orchestration&lt;/strong&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Choreography (event-driven)
&lt;/h4&gt;

&lt;p&gt;Services emit domain events, i.e. OrderCreated, InventoryReserved, PaymentFailed, etc. Other interested services subscribe to these events and can create a compensating transaction if needed. i.e. when a PaymentFailed event is published, the Inventory service creates a compensating transaction to free reserved items, the Order service updates its order status to failed, and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8pncrxizfkl0wf9r055.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8pncrxizfkl0wf9r055.png" alt="Choreography Saga" width="568" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Pros
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Natural for event-driven systems.&lt;/li&gt;
&lt;li&gt;Simple to start and each service is decoupled.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Cons
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;Harder to manage complex flows, timeouts, and branching.&lt;/li&gt;
&lt;li&gt;Risk of spaghetti events: imagine if just 5 services are involved, and each service could listen to the others!&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Orchestration (command-driven)
&lt;/h4&gt;

&lt;p&gt;A central orchestrator sends commands to other services and waits/listens for results in a non-blocking manner.&lt;br&gt;
The workflow is explicit code from domain logic, centralized to manage retries, timeouts, compensations, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4ff1wingpz6tngpepmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff4ff1wingpz6tngpepmj.png" alt="Orchestration Saga" width="675" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h5&gt;
  
  
  Pros
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;One source of truth, i.e. single place for flow, retries, and timeouts.&lt;/li&gt;
&lt;li&gt;Clear visibility even with complex processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h5&gt;
  
  
  Cons
&lt;/h5&gt;

&lt;ul&gt;
&lt;li&gt;The orchestrator could be a single point of failure, so it is a critical component.&lt;/li&gt;
&lt;li&gt;Slightly tighter coupling with the orchestrator API, as typically it is a 3rd-party library/component.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Saga in both modes shares some common pros and cons.&lt;/p&gt;

&lt;h4&gt;
  
  
  Pros
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Saga fits microservices naturally as each microservice is loosely coupled from the others.&lt;/li&gt;
&lt;li&gt;Saga is non-blocking by the nature of its communication, which is critical for the scalability of microservices.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Cons
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Eventual consistency: the system is temporarily inconsistent until all steps (or compensations) finish.&lt;/li&gt;
&lt;li&gt;Complex compensation logic: “undoing” business actions is not always straightforward.&lt;/li&gt;
&lt;li&gt;Increased complexity: i.e. handling idempotency and compensating logic.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In this article, we looked at the problem of distributed transactions, why 2PC is not suitable for microservices, and how the Saga pattern (choreography and orchestration) helps solve it.&lt;/p&gt;

&lt;p&gt;In the next article, we’ll start implementing the solution using Temporal as an orchestrator-based Saga. We’ll walk through code examples and best practices step by step.&lt;/p&gt;

&lt;p&gt;👉 Follow this series if you’re interested in building resilient microservices, and feel free to share your thoughts or questions in the comments — I’d love to hear your perspective.&lt;/p&gt;

</description>
      <category>microservices</category>
      <category>distributedsystems</category>
      <category>saga</category>
    </item>
  </channel>
</rss>
