← Back to list

Implementing Choreography and Orchestration-Based Saga Patterns in Spring Boot — I

The Saga design pattern is a distributed systems pattern used to manage long-running, complex transactions across multiple microservices…

Uma Charan Gorai · 2025-08-22 15:14 · 6 claps · 11.1 min read
#saga-pattern #saga #microservices #microservice-patterns #spring-boot
Open on Medium ↗
Wiki topics: 🏃 · Running & Endurance

Implementing Choreography and Orchestration-Based Saga Patterns in Spring Boot — I

The Saga design pattern is a distributed systems pattern used to manage long-running, complex transactions across multiple microservices. Unlike traditional ACID transactions, which rely on a centralized database to ensure atomicity, consistency, isolation, and durability, sagas handle transactions in a distributed environment where each service manages its own database. A saga breaks a transaction into a series of local transactions, each executed by an individual microservice, with compensating actions to handle failures. This pattern ensures eventual consistency rather than immediate consistency.

The Saga pattern is particularly useful in microservices architectures where services are loosely coupled, and a single business operation spans multiple services. It avoids the need for distributed two-phase commits, which can be complex and prone to failures.

There are two primary types of Saga design patterns: Choreography-based Saga and Orchestration-based Saga. Below, I explain both in detail, including their mechanisms, advantages, and challenges.

1. Choreography-based Saga

Definition

In a choreography-based saga, there is no central coordinator. Instead, each microservice involved in the saga produces and listens to events or messages. When a service completes its local transaction, it emits an event to notify other services, which then decide what action to take next based on those events. The flow of the saga is determined by the events exchanged between services, resembling a decentralized, event-driven choreography.

How It Works

  1. Event Trigger: A saga begins when an initial event or command triggers the first microservice to perform its local transaction.
  2. Local Transaction Execution: The microservice executes its local transaction and updates its database.
  3. Event Emission: Upon successful completion (or failure), the service publishes an event (e.g., via a message broker like Kafka, RabbitMQ, or AWS SNS/SQS) to inform other services.
  4. Event Consumption: Other microservices subscribed to these events react by performing their own local transactions and publishing new events.
  5. Compensation on Failure: If a failure occurs, a service may publish a failure event, triggering compensating transactions in previously executed steps to undo changes (rollback-like behavior).
  6. Completion: The saga completes when all services have successfully executed their transactions or when compensating transactions have undone changes due to a failure.

Example

Consider an e-commerce order processing system:

  • Step 1: The Order Service creates an order and publishes an OrderCreated event.
  • Step 2: The Payment Service listens for OrderCreated, processes the payment, and publishes a PaymentProcessed event.
  • Step 3: The Inventory Service listens for PaymentProcessed, reserves inventory, and publishes an InventoryReserved event.
  • Step 4: The Shipping Service listens for InventoryReserved and schedules delivery.
  • If the Inventory Service fails (e.g., insufficient stock), it publishes an InventoryFailed event, triggering the Payment Service to refund the payment and the Order Service to cancel the order.

Advantages

  • Decentralized: No single point of failure, as there’s no central coordinator.
  • Loose Coupling: Services communicate via events, reducing direct dependencies.
  • Scalability: Services can process events asynchronously, improving performance.
  • Flexibility: Easy to add new services or steps by subscribing to relevant events.

Challenges

  • Complexity in Tracking: Without a central coordinator, it’s harder to monitor the saga’s state or debug issues, as the flow is distributed across services.
  • Eventual Consistency: Services rely on eventual consistency, which may lead to temporary inconsistencies.
  • Error Handling: Compensating transactions must be carefully designed to undo changes, and handling partial failures can be complex.
  • Event Proliferation: A large number of events can make the system harder to manage and understand.

When to Use

  • Suitable for systems with a small number of services or straightforward workflows.
  • Ideal when services need high autonomy and loose coupling.
  • Works well in event-driven architectures where services already communicate via a message broker.

2. Orchestration-based Saga

Definition

In an orchestration-based saga, a central coordinator (orchestrator) manages the entire saga. The orchestrator is responsible for invoking each microservice’s local transaction in sequence, tracking the saga’s state, and handling failures by initiating compensating transactions. The orchestrator explicitly defines the workflow, making it a command-driven approach.

How It Works

  1. Orchestrator Initiation: The saga begins when the orchestrator receives a request to execute a business operation.
  2. Sequential Execution: The orchestrator sends commands to each microservice in a predefined sequence to perform their local transactions.
  3. State Tracking: The orchestrator maintains the state of the saga (e.g., in a database or in-memory) and tracks the success or failure of each step.
  4. Compensation on Failure: If a service fails, the orchestrator triggers compensating transactions for all previously completed steps to undo changes.
  5. Completion: The saga completes when all steps succeed or when compensating transactions revert changes due to a failure.

Example

Using the same e-commerce order processing system:

  • Step 1: The Saga Orchestrator receives an order request and sends a command to the Order Service to create an order.
  • Step 2: Upon success, the orchestrator commands the Payment Service to process the payment.
  • Step 3: If the payment succeeds, the orchestrator commands the Inventory Service to reserve inventory.
  • Step 4: Finally, the orchestrator commands the Shipping Service to schedule delivery.
  • If the Inventory Service fails (e.g., insufficient stock), the orchestrator sends commands to the Payment Service to refund the payment and the Order Service to cancel the order.

Advantages

  • Centralized Control: The orchestrator simplifies tracking and debugging, as the saga’s state and flow are managed in one place.
  • Clear Workflow: The sequence of steps is explicitly defined, making it easier to understand and modify.
  • Easier Error Handling: The orchestrator can manage compensating transactions systematically.
  • Monitoring: Centralized state management makes it easier to monitor saga progress and detect issues.

Challenges

  • Single Point of Failure: The orchestrator can become a bottleneck or point of failure if not designed for high availability.
  • Tight Coupling: Services may become dependent on the orchestrator, reducing autonomy.
  • Scalability Concerns: The orchestrator must handle the load of coordinating multiple services, which can impact performance.
  • Complexity in Orchestrator: The orchestrator’s logic can become complex, especially for large sagas with many steps.

When to Use

  • Suitable for complex workflows with many steps or conditional logic.
  • Ideal when centralized control and monitoring are priorities.
  • Works well when the saga requires strict sequencing or complex error handling.

Practical Considerations

I’ll implement a Choreography-based Saga using Spring Boot and Maven, with configuration in application.properties. The example will continue with the e-commerce order processing scenario, involving Order Service, Payment Service, Inventory Service, and Shipping Service. Each service will communicate via events using a message broker (Kafka in this case). I’ll explain each step clearly and provide the necessary code wrapped in tags.

Scenario

An e-commerce system processes an order with the following flow:

  1. Order Service: Creates an order and publishes an OrderCreated event.
  2. Payment Service: Listens for OrderCreated, processes the payment, and publishes a PaymentProcessed or PaymentFailed event.
  3. Inventory Service: Listens for PaymentProcessed, reserves inventory, and publishes an InventoryReserved or InventoryFailed event.
  4. Shipping Service: Listens for InventoryReserved and schedules delivery.
  5. Compensating Transactions: If any step fails (e.g., InventoryFailed), previous services perform compensating actions (e.g., refund payment, cancel order).

Prerequisites

  • Spring Boot: For building microservices.
  • Apache Kafka: As the message broker for event-driven communication.
  • Maven: For dependency management.
  • H2 Database: For simplicity, each service uses an in-memory H2 database.
  • Spring Kafka: For publishing and consuming events.

Step-by-Step Implementation

Step 1: Set Up the Project Structure

Each service (Order, Payment, Inventory, Shipping) will be a separate Spring Boot application. For brevity, I’ll provide key components for each service, focusing on one Maven project per service with shared configurations. You can replicate the setup for each service.

Directory Structure (for each service):

order-service/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/orderservice/
│   │   │       ├── OrderServiceApplication.java
│   │   │       ├── model/
│   │   │       ├── repository/
│   │   │       ├── service/
│   │   │       ├── event/
│   │   │       └── controller/
│   │   ├── resources/
│   │       └── application.properties
├── pom.xml

Step 2: Maven Configuration (Common for All Services)

Each service uses a similar pom.xml with dependencies for Spring Boot, Spring Kafka, and H2.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>order-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>order-service</name>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.2</version>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Explanation:

  • Dependencies: Include spring-boot-starter-web for REST APIs, spring-boot-starter-data-jpa for database access, spring-kafka for Kafka integration, and h2 for the in-memory database.
  • Lombok: Simplifies boilerplate code (getters, setters).
  • Spring Boot Version: 3.3.2 for the latest stable features.
  • Note: Replace artifactId (e.g., order-service) with payment-service, inventory-service, or shipping-service for respective services.

Step 3: Application Properties (Common for All Services)

Configure Kafka and H2 database settings in application.properties.

# 

spring.datasource.url=jdbc:h2:mem:testdb 

spring.datasource.driverClassName=org.h2.Driver 

spring.datasource.username=sa 

spring.datasource.password= 

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect 

spring.h2.console.enabled=true

spring.kafka.bootstrap-servers=localhost:9092 

spring.kafka.producer.key-serializer=org.apache.kafka.common.serialization.StringSerializer 

spring.kafka.producer.value-serializer=org.springframework.kafka.support.serializer.JsonSerializer 

spring.kafka.consumer.group-id=${spring.application.name} 

spring.kafka.consumer.auto-offset-reset=earliest 

spring.kafka.consumer.key-deserializer=org.apache.kafka.common.serialization.StringDeserializer 

spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer 

spring.kafka.consumer.properties.spring.json.trusted.packages=\*

spring.application.name=order-service

Explanation:

  • H2 Database: Configures an in-memory database for simplicity.
  • Kafka: Sets up the Kafka broker at localhost:9092 (assumes a local Kafka server is running). Serializers/deserializers handle JSON events.
  • Consumer Group: Uses spring.application.name as the Kafka consumer group ID, which should be unique per service (e.g., order-service, payment-service).
  • Trusted Packages: Allows JSON deserialization for all packages (adjust in production for security).
  • Note: Update spring.application.name for each service (e.g., payment-service).

Step 4: Order Service Implementation

The Order Service initiates the saga by creating an order and publishing an OrderCreated event.

Model: Order

package com.example.orderservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Data;

@Entity
@Data
public class Order {
    @Id
    private String orderId;
    private String customerId;
    private double amount;
    private String status; // e.g., CREATED, CANCELLED
}

Event: OrderCreated

package com.example.orderservice.event;

import lombok.Data;

@Data
public class OrderCreatedEvent {
    private String orderId;
    private String customerId;
    private double amount;
}

Repository: OrderRepository

package com.example.orderservice.repository;

import com.example.orderservice.model.Order;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, String> {
}

Service: OrderService

package com.example.orderservice.service;

import com.example.orderservice.event.OrderCreatedEvent;
import com.example.orderservice.model.Order;
import com.example.orderservice.repository.OrderRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderRepository orderRepository;
    private final KafkaTemplate<String, Object> kafkaTemplate;

    public Order createOrder(Order order) {
        order.setStatus("CREATED");
        orderRepository.save(order);
        OrderCreatedEvent event = new OrderCreatedEvent();
        event.setOrderId(order.getOrderId());
        event.setCustomerId(order.getCustomerId());
        event.setAmount(order.getAmount());
        kafkaTemplate.send("order-topic", event);
        return order;
    }

    @KafkaListener(topics = "inventory-failed-topic")
    public void handleInventoryFailed(String orderId) {
        Order order = orderRepository.findById(orderId).orElseThrow();
        order.setStatus("CANCELLED");
        orderRepository.save(order);
    }
}

Controller: OrderController

package com.example.orderservice.controller;

import com.example.orderservice.model.Order;
import com.example.orderservice.service.OrderService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/orders")
@RequiredArgsConstructor
public class OrderController {
    private final OrderService orderService;

    @PostMapping
    public Order createOrder(@RequestBody Order order) {
        return orderService.createOrder(order);
    }
}

Main Application: OrderServiceApplication

package com.example.orderservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.kafka.annotation.EnableKafka;

@SpringBootApplication
@EnableKafka
public class OrderServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(OrderServiceApplication.class, args);
    }
}

Explanation:

  • Model: Order represents the order entity stored in the H2 database.
  • Event: OrderCreatedEvent carries order details to the next service.
  • Repository: OrderRepository handles CRUD operations for orders.
  • Service: OrderService creates an order, saves it, and publishes an OrderCreatedEvent to the order-topic. It also listens for inventory-failed-topic to cancel the order if inventory reservation fails.
  • Controller: Exposes a REST endpoint to create orders.
  • Main: Enables Kafka with @EnableKafka.

Step 5: Payment Service Implementation

The Payment Service listens for OrderCreated events, processes payments, and publishes PaymentProcessed or PaymentFailed events.

Model: Payment

package com.example.paymentservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Data;

@Entity
@Data
public class Payment {
    @Id
    private String paymentId;
    private String orderId;
    private double amount;
    private String status; // e.g., PROCESSED, REFUNDED
}

Event: PaymentProcessedEvent

package com.example.paymentservice.event;

import lombok.Data;

@Data
public class PaymentProcessedEvent {
    private String orderId;
    private String paymentId;
}

Event: PaymentFailedEvent

package com.example.paymentservice.event;

import lombok.Data;

@Data
public class PaymentFailedEvent {
    private String orderId;
    private String paymentId;
}

Repository: PaymentRepository

package com.example.paymentservice.repository;

import com.example.paymentservice.model.Payment;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PaymentRepository extends JpaRepository<Payment, String> {
}

Service: PaymentService

package com.example.paymentservice.service;

import com.example.paymentservice.event.OrderCreatedEvent;
import com.example.paymentservice.event.PaymentFailedEvent;
import com.example.paymentservice.event.PaymentProcessedEvent;
import com.example.paymentservice.model.Payment;
import com.example.paymentservice.repository.PaymentRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class PaymentService {
    private final PaymentRepository paymentRepository;
    private final KafkaTemplate<String, Object> kafkaTemplate;

    @KafkaListener(topics = "order-topic")
    public void processPayment(OrderCreatedEvent event) {
        Payment payment = new Payment();
        payment.setPaymentId("PAY_" + event.getOrderId());
        payment.setOrderId(event.getOrderId());
        payment.setAmount(event.getAmount());
        payment.setStatus("PROCESSED");
        paymentRepository.save(payment);

        // Simulate payment failure for demonstration
        if (event.getAmount() > 1000) {
            payment.setStatus("FAILED");
            paymentRepository.save(payment);
            PaymentFailedEvent failedEvent = new PaymentFailedEvent();
            failedEvent.setOrderId(event.getOrderId());
            failedEvent.setPaymentId(payment.getPaymentId());
            kafkaTemplate.send("payment-failed-topic", failedEvent);
        } else {
            PaymentProcessedEvent processedEvent = new PaymentProcessedEvent();
            processedEvent.setOrderId(event.getOrderId());
            processedEvent.setPaymentId(payment.getPaymentId());
            kafkaTemplate.send("payment-processed-topic", processedEvent);
        }
    }

    @KafkaListener(topics = "inventory-failed-topic")
    public void refundPayment(String orderId) {
        Payment payment = paymentRepository.findById("PAY_" + orderId).orElseThrow();
        payment.setStatus("REFUNDED");
        paymentRepository.save(payment);
    }
}

Main Application: PaymentServiceApplication

package com.example.paymentservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.kafka.annotation.EnableKafka;

@SpringBootApplication
@EnableKafka
public class PaymentServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(PaymentServiceApplication.class, args);
    }
}

Explanation:

  • Model: Payment stores payment details.
  • Events: PaymentProcessedEvent and PaymentFailedEvent signal success or failure.
  • Repository: Manages payment persistence.
  • Service: Listens for order-topic, processes payments, and publishes to payment-processed-topic or payment-failed-topic. It also listens for inventory-failed-topic to refund payments.
  • Failure Simulation: Payments fail (for demo purposes) if the amount exceeds 1000.

Step 6: Inventory Service Implementation

The Inventory Service listens for PaymentProcessed events, reserves inventory, and publishes InventoryReserved or InventoryFailed events.

Model: Inventory

package com.example.inventoryservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Data;

@Entity
@Data
public class Inventory {
    @Id
    private String inventoryId;
    private String orderId;
    private String productId;
    private int quantity;
    private String status; // e.g., RESERVED, CANCELLED
}

Event: InventoryReservedEvent

package com.example.inventoryservice.event;

import lombok.Data;

@Data
public class InventoryReservedEvent {
    private String orderId;
    private String inventoryId;
}

Event: InventoryFailedEvent

package com.example.inventoryservice.event;

import lombok.Data;

@Data
public class InventoryFailedEvent {
    private String orderId;
    private String inventoryId;
}

Repository: InventoryRepository

package com.example.inventoryservice.repository;

import com.example.inventoryservice.model.Inventory;
import org.springframework.data.jpa.repository.JpaRepository;

public interface InventoryRepository extends JpaRepository<Inventory, String> {
}

Service: InventoryService

package com.example.inventoryservice.service;

import com.example.inventoryservice.event.InventoryFailedEvent;
import com.example.inventoryservice.event.InventoryReservedEvent;
import com.example.inventoryservice.event.PaymentProcessedEvent;
import com.example.inventoryservice.model.Inventory;
import com.example.inventoryservice.repository.InventoryRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class InventoryService {
    private final InventoryRepository inventoryRepository;
    private final KafkaTemplate<String, Object> kafkaTemplate;

    @KafkaListener(topics = "payment-processed-topic")
    public void reserveInventory(PaymentProcessedEvent event) {
        Inventory inventory = new Inventory();
        inventory.setInventoryId("INV_" + event.getOrderId());
        inventory.setOrderId(event.getOrderId());
        inventory.setProductId("PROD_1");
        inventory.setQuantity(1);
        inventory.setStatus("RESERVED");
        inventoryRepository.save(inventory);

        // Simulate inventory failure for demonstration
        if (Math.random() > 0.7) { // 30% chance of failure
            inventory.setStatus("FAILED");
            inventoryRepository.save(inventory);
            InventoryFailedEvent failedEvent = new InventoryFailedEvent();
            failedEvent.setOrderId(event.getOrderId());
            failedEvent.setInventoryId(inventory.getInventoryId());
            kafkaTemplate.send("inventory-failed-topic", event.getOrderId(), failedEvent);
        } else {
            InventoryReservedEvent reservedEvent = new InventoryReservedEvent();
            reservedEvent.setOrderId(event.getOrderId());
            reservedEvent.setInventoryId(inventory.getInventoryId());
            kafkaTemplate.send("inventory-reserved-topic", reservedEvent);
        }
    }
}

Main Application: InventoryServiceApplication

package com.example.inventoryservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.kafka.annotation.EnableKafka;

@SpringBootApplication
@EnableKafka
public class InventoryServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(InventoryServiceApplication.class, args);
    }
}

Explanation:

  • Model: Inventory tracks reserved items.
  • Events: InventoryReservedEvent and InventoryFailedEvent signal success or failure.
  • Repository: Manages inventory persistence.
  • Service: Listens for payment-processed-topic, reserves inventory, and publishes to inventory-reserved-topic or inventory-failed-topic.
  • Failure Simulation: Randomly fails 30% of the time for demonstration.

Step 7: Shipping Service Implementation

The Shipping Service listens for InventoryReserved events and schedules delivery.

Model: Shipment

package com.example.shippingservice.model;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import lombok.Data;

@Entity
@Data
public class Shipment {
    @Id
    private String shipmentId;
    private String orderId;
    private String status; // e.g., SCHEDULED
}

Repository: ShipmentRepository

package com.example.shippingservice.repository;

import com.example.shippingservice.model.Shipment;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ShipmentRepository extends JpaRepository<Shipment, String> {
}

Service: ShippingService

package com.example.shippingservice.service;

import com.example.shippingservice.event.InventoryReservedEvent;
import com.example.shippingservice.model.Shipment;
import com.example.shippingservice.repository.ShipmentRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class ShippingService {
    private final ShipmentRepository shipmentRepository;

    @KafkaListener(topics = "inventory-reserved-topic")
    public void scheduleShipment(InventoryReservedEvent event) {
        Shipment shipment = new Shipment();
        shipment.setShipmentId("SHIP_" + event.getOrderId());
        shipment.setOrderId(event.getOrderId());
        shipment.setStatus("SCHEDULED");
        shipmentRepository.save(shipment);
    }
}

Main Application: ShippingServiceApplication

package com.example.shippingservice;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.kafka.annotation.EnableKafka;

@SpringBootApplication
@EnableKafka
public class ShippingServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(ShippingServiceApplication.class, args);
    }
}

Explanation:

  • Model: Shipment tracks delivery scheduling.
  • Repository: Manages shipment persistence.
  • Service: Listens for inventory-reserved-topic and schedules a shipment.
  • Note: No failure simulation here for simplicity, but you could add it similarly.

Step 8: Running the System

  1. Start Kafka: Ensure a Kafka server is running (e.g., localhost:9092). Create topics: order-topic, payment-processed-topic, payment-failed-topic, inventory-reserved-topic, inventory-failed-topic.
  2. Run Services: Start each Spring Boot application (OrderServiceApplication, PaymentServiceApplication, etc.).
  3. Test the Saga:
{
    "orderId": "ORD_1",
    "customerId": "CUST_1",
    "amount": 500
}
  • If amount <= 1000 and inventory doesn’t fail, the saga completes (order created, payment processed, inventory reserved, shipment scheduled).
  • If amount > 1000 or inventory fails (30% chance), compensating transactions occur (payment refunded, order cancelled).

Step 9: Explanation of Saga Flow

  1. Order Creation: The Order Service creates an order and publishes OrderCreatedEvent to order-topic.
  2. Payment Processing: The Payment Service consumes OrderCreatedEvent, processes the payment, and publishes either PaymentProcessedEvent or PaymentFailedEvent.
  3. Inventory Reservation: The Inventory Service consumes PaymentProcessedEvent, reserves inventory, and publishes InventoryReservedEvent or InventoryFailedEvent.
  4. Shipment Scheduling: The Shipping Service consumes InventoryReservedEvent and schedules delivery.
  5. Compensating Transactions:
  • If payment fails, the Order Service cancels the order (listens to payment-failed-topic).
  • If inventory fails, the Payment Service refunds the payment, and the Order Service cancels the order (both listen to inventory-failed-topic).

Notes

  • Idempotency: Ensure services handle duplicate events (e.g., by checking existing records before processing).
  • Error Handling: Compensating transactions are implemented in OrderService and PaymentService to handle failures.
  • Scalability: Kafka ensures asynchronous, scalable event communication.
  • Monitoring: Use tools like Zipkin or Jaeger for distributed tracing.
  • Production Considerations: Replace H2 with a persistent database (e.g., PostgreSQL) and secure Kafka with SSL.

This implementation demonstrates a choreography-based saga where services communicate via events without a central orchestrator, ensuring loose coupling and scalability.

[embed]Implementing Choreography and Orchestration-Based Saga Patterns in Spring Boot — II I’ll implement an Orchestration-based Saga using Spring Boot, Maven, and application.properties for the same e-commerce…medium.com


메타데이터
post_id
f3e7ca96d601
slug
implementing-choreography-and-orchestration-based-saga-patterns-in-spring-boot-i-f3e7ca96d601
url
https://medium.com/@ucgorai/implementing-choreography-and-orchestration-based-saga-patterns-in-spring-boot-i-f3e7ca96d601
canonical_url
https://medium.com/@ucgorai/implementing-choreography-and-orchestration-based-saga-patterns-in-spring-boot-i-f3e7ca96d601
author_url
https://medium.com/@ucgorai
status
ok
fetched_at
2026-06-18 00:10:23