Java Microservices - Saga Pattern



Introduction

As businesses embrace microservices architecture, one major challenge arises: how to maintain data consistency across distributed services. In traditional monoliths, a database transaction ensures ACID properties. But in microservices, each service often manages its own database − making distributed transactions difficult.

The Saga pattern is a solution to this problem. It allows services to collaborate on a long-running business transaction by exchanging a sequence of local transactions and compensating actions when needed.

This article explores the Saga pattern in detail, including its types, real-world examples, implementation with Spring Boot, and best practices.

What is Saga Pattern?

A Saga is a sequence of local transactions, where each transaction updates data within a single microservice and publishes an event or calls the next service. If one transaction fails, the Saga executes compensating transactions to undo the impact of previous ones.

A saga is a failure management pattern for long-running distributed transactions.

Why Do We Need Sagas?

Challenges in Distributed Transactions

Sr.No. Challenge Description
1 Lack of global transactions No XA/2PC (Two Phase Commit) across microservices
2 Data ownership Each service owns its data (Database per service)
3 Partial failures Some steps may succeed, others may fail
4 Consistency Eventual consistency instead of strict ACID

The Saga pattern helps orchestrate distributed workflows with eventual consistency.

Types of Saga Implementations

Choreography Based Saga

  • No central controller

  • Services listen to events and act accordingly

  • Lightweight, but complex with many services

Example Flow

  1. Order Service → emits OrderCreated

  2. Payment Service → listens, processes payment → emits PaymentCompleted

  3. Inventory Service → reserves stock → emits InventoryReserved

  4. Shipping Service → ships item

If any step fails, a compensating event is triggered.

Orchestration-Based Saga

  • Central Saga orchestrator directs the flow

  • Each service executes commands from the orchestrator

  • Easier to manage, but introduces coupling

Example Flow

  1. Orchestrator → calls Order Service

  2. On success → calls Payment Service

  3. On failure → instructs Order Service to cancel

Real-World Example: E-Commerce Order Processing

Steps

  1. Place Order

  2. Reserve Inventory

  3. Process Payment

  4. Ship Item

Each service has a local database and transaction logic.

If payment fails, we must −

  1. Cancel the order

  2. Release the inventory

This is handled by a Saga.

Saga architecture

Diagram: Choreography Based Saga

Choreography Based Saga

Each service publishes and subscribes to events through a broker like Kafka or RabbitMQ.

Implementing Saga Pattern in Spring Boot

Let's implement a Choreography based saga using Spring Boot + Kafka.

Technologies Used

  • Spring Boot

  • Spring Kafka

  • Apache Kafka (as the event broker)

  • Lombok for model simplification

Maven Dependencies

<dependency>
   <groupId>org.springframework.kafka</groupId>
   <artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <scope>provided</scope>
</dependency>

Example Services and Topics

Sr.No. Service Events Published Topics Subscribed
1 Order Service OrderCreated, OrderCancelled PaymentFailed, InventoryFailed
2 Payment Service PaymentCompleted, PaymentFailed OrderCreated
3 Inventory Service InventoryReserved, InventoryFailed PaymentCompleted

Sample Event: OrderCreatedEvent.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class OrderCreatedEvent {
   private String orderId;
   private String productId;
   private int quantity;
}

Order Service − Kafka Producer

@Service
public class OrderService {

   @Autowired
   private KafkaTemplate<String, Object> kafkaTemplate;

   public void createOrder(OrderCreatedEvent event) {
      kafkaTemplate.send("order-created", event);
   }
}

Payment Service − Kafka Consumer

@KafkaListener(topics = "order-created", groupId = "payment-service")
public void handleOrder(OrderCreatedEvent event) {
   // Process payment
   boolean success = processPayment(event);
   if (success) {
      kafkaTemplate.send("payment-completed", new PaymentCompletedEvent(event.getOrderId()));
   } else {
      kafkaTemplate.send("payment-failed", new PaymentFailedEvent(event.getOrderId()));
   }
}

Inventory Service − Kafka Consumer

@KafkaListener(topics = "payment-completed", groupId = "inventory-service")
public void handlePayment(PaymentCompletedEvent event) {
   // Reserve inventory
   boolean success = reserveStock(event.getOrderId());
   if (success) {
      kafkaTemplate.send("inventory-reserved", new InventoryReservedEvent(event.getOrderId()));
   } else {
      kafkaTemplate.send("inventory-failed", new InventoryFailedEvent(event.getOrderId()));
   }
}

Saga Compensation and Failure Handling

Compensating Transactions

If a step fails (e.g., inventory reservation), previous actions must be reversed−

  • InventoryFailed → triggers PaymentRollback

  • PaymentFailed → triggers OrderCancelled

These compensating actions must be idempotent and safe to retry.

Benefits of the Saga Pattern

Sr.No. Benefit Description
1 Decentralized workflow Maintains autonomy of microservices
2 Resilience Can recover from partial failures
3 Eventual consistency Instead of strict ACID transactions
4 Scalable and fault-tolerant Built on asynchronous messaging

Challenges and Pitfalls

Sr.No. Challenge Mitigation
1 Complex error handling Use retries and DLQs
2 Debugging flows Use tracing tools like Zipkin
3 Compensating logic overhead Modularize and isolate business logic
4 Message ordering issues Use Kafka partitions wisely

Testing a Saga

Approaches

  • Use Testcontainers to simulate Kafka or RabbitMQ

  • Verify event flow using integration tests

  • Mock downstream services using WireMock

  • Simulate failures to test compensation logic

Real-World Examples

Sr.No. Company Use of Saga Pattern
1 Netflix Manages distributed workflows in video delivery
2 Booking.com Manages hotel bookings, payments, and cancellations
3 Uber Handles driver assignment, payments, and cancellations
4 Amazon Processes multi-step order and inventory systems

Best Practices

Sr.No. Practice Reason
1 Use separate event models Avoid domain model leakage
2 Make compensating actions idempotent Safe retries
3 Implement timeouts Avoid stuck sagas
4 Track saga state Use DB or state store
5 Use correlation IDs Easier debugging and tracing

Conclusion

The Saga pattern provides an elegant solution to the problem of distributed transactions in a microservices architecture. Whether using choreography or orchestration, sagas enable services to maintain data consistency, handle failures gracefully, and ensure resilient workflows.

By combining Spring Boot with Kafka or orchestration engines, developers can build reliable, scalable, and maintainable systems that operate effectively across service boundaries.

Advertisements