spring-boot-saga-pattern · diff
git:20260316.91dc3c4 to git:20260324.a02e282
197 added, 268 removed. Audit A to A.
---
name: spring-boot-saga-pattern
- description: Provides distributed transaction patterns using the Saga Pattern in Spring Boot microservices. Use when building microservices requiring transaction management across multiple services, handling compensating transactions, ensuring eventual consistency, or implementing choreography or orchestration-based sagas with Spring Boot, Kafka, or Axon Framework.
+ description: Provides distributed transaction patterns using the Saga Pattern for Spring Boot microservices. Use when implementing distributed transactions across services, handling compensating transactions, ensuring eventual consistency, or building choreography or orchestration-based sagas with Kafka, RabbitMQ, or Axon Framework.
allowed-tools: Read, Write, Edit, Bash, Glob, Grep
---
# Spring Boot Saga Pattern
- ## When to Use
+ ## Overview
- Implement this skill when:
+ Implements distributed transactions across microservices using the Saga Pattern. Replaces two-phase commit with a sequence of local transactions and compensating actions. Supports choreography (event-driven) and orchestration (centralized coordinator) approaches with Kafka, RabbitMQ, or Axon Framework.
+ ## When to Use
+
- Building distributed transactions across multiple microservices
- - Needing to replace two-phase commit (2PC) with a more scalable solution
- - Handling transaction rollback when a service fails in multi-service workflows
+ - Replacing two-phase commit (2PC) with a more scalable solution
+ - Handling transaction rollback when a service fails
- Ensuring eventual consistency in microservices architecture
- Implementing compensating transactions for failed operations
- Coordinating complex business processes spanning multiple services
- - Choosing between choreography-based and orchestration-based saga approaches
**Trigger phrases**: distributed transactions, saga pattern, compensating transactions, microservices transaction, eventual consistency, rollback across services, orchestration pattern, choreography pattern
- ## Overview
-
- The **Saga Pattern** is an architectural pattern for managing distributed transactions in microservices. Instead of using a single ACID transaction across multiple databases, a saga breaks the transaction into a sequence of local transactions. Each local transaction updates its database and publishes an event or message to trigger the next step. If a step fails, the saga executes **compensating transactions** to undo the changes made by previous steps.
-
- ### Key Architectural Decisions
-
- When implementing a saga, make these decisions:
-
- 1. **Approach Selection**: Choose between **choreography-based** (event-driven, decoupled) or **orchestration-based** (centralized control, easier to track)
- 2. **Messaging Platform**: Select Kafka, RabbitMQ, or Spring Cloud Stream
- 3. **Framework**: Use Axon Framework, Eventuate Tram, Camunda, or Apache Camel
- 4. **State Persistence**: Store saga state in database for recovery and debugging
- 5. **Idempotency**: Ensure all operations (especially compensations) are idempotent and retryable
-
## Instructions
- Follow these steps to implement saga pattern for distributed transactions:
-
- ### 1. Define Transaction Flow
-
- Identify all services involved in the business process. Map out the sequence of local transactions and their corresponding compensating transactions.
-
- ### 2. Choose Saga Approach
-
- Select choreography (event-driven, decentralized) or orchestration (centralized coordinator) based on team expertise and system complexity.
-
- ### 3. Design Domain Events
-
- Create events for each transaction step (OrderCreated, PaymentProcessed, InventoryReserved). Include correlationId for tracing.
+ ### 1. Design Transaction Flow
- ### 4. Implement Local Transactions
+ Map the sequence of operations and their compensating transactions:
- Ensure each service can complete its local transaction atomically within its own database boundary.
+ ```
+ Order → Payment → Inventory → Shipment
+ ↓ ↓ ↓ ↓
+ Cancel Refund Release Cancel
+ ```
- ### 5. Define Compensating Transactions
+ **Validation**: Verify every forward step has a corresponding compensation.
- For each forward operation, implement a compensating operation that reverses the effect (cancel order, refund payment, release inventory).
+ ### 2. Choose Implementation Approach
- ### 6. Set Up Message Broker
+ | Approach | Use Case | Stack |
+ |----------|----------|-------|
+ | Choreography | Greenfield, few participants | Spring Cloud Stream + Kafka/RabbitMQ |
+ | Orchestration | Complex workflows, brownfield | Axon Framework, Eventuate Tram, Camunda |
- Configure Kafka or RabbitMQ with appropriate topics/queues. Implement idempotent message consumers.
+ **Validation**: Review team expertise and system complexity before choosing.
- ### 7. Implement Orchestrator (if using orchestration)
+ ### 3. Implement Services with Local Transactions
- Create a saga orchestrator service that tracks saga state, sends commands to participants, and handles compensations on failure.
+ Each service completes its local ACID transaction atomically:
- ### 8. Configure Choreography (if using choreography)
+ ```java
+ @Service
+ @RequiredArgsConstructor
+ public class OrderService {
+ private final OrderRepository orderRepository;
+ private final KafkaTemplate<String, Object> kafka;
- Set up event listeners in each service that react to events from other services and trigger next steps.
+ @Transactional
+ public Order createOrder(CreateOrderCommand cmd) {
+ Order order = orderRepository.save(new Order(cmd.orderId(), cmd.items()));
+ kafka.send("order.created", new OrderCreatedEvent(order.getId(), order.getItems()));
+ return order;
+ }
+ }
+ ```
- ### 9. Handle Timeouts
+ **Validation**: Test that local transaction commits before event is published.
- Implement timeout mechanisms for each saga step. Configure dead-letter queues for messages that exceed processing time limits.
+ ### 4. Implement Compensating Transactions
- ### 10. Add Monitoring
+ Every forward operation requires an idempotent compensation:
- Track saga execution status, duration, and failure rates. Set up alerts for stuck or failed sagas.
+ ```java
+ @Service
+ @RequiredArgsConstructor
+ public class PaymentService {
+ private final PaymentRepository paymentRepository;
+ private final KafkaTemplate<String, Object> kafka;
- ## Two Approaches to Implement Saga
+ public void processPayment(PaymentRequest request) {
+ Payment payment = paymentRepository.save(new Payment(request.orderId(), request.amount()));
+ kafka.send("payment.processed", new PaymentProcessedEvent(payment.getId(), request.orderId()));
+ }
- ### Choreography-Based Saga
+ @Transactional
+ public void refundPayment(String paymentId) {
+ paymentRepository.findById(paymentId)
+ .ifPresent(p -> {
+ p.setStatus(REFUNDED);
+ paymentRepository.save(p);
+ kafka.send("payment.refunded", new PaymentRefundedEvent(paymentId));
+ });
+ }
+ }
+ ```
- Each microservice publishes events and listens to events from other services. **No central coordinator**.
+ **Validation**: Confirm compensation can execute safely multiple times (idempotency).
- **Best for**: Greenfield microservice applications with few participants
+ ### 5. Set Up Message Broker
- **Advantages**:
- - Simple for small number of services
- - Loose coupling between services
- - No single point of failure
+ Configure Kafka with idempotent consumers:
- **Disadvantages**:
- - Difficult to track workflow state
- - Hard to troubleshoot and maintain
- - Complexity grows with number of services
+ ```java
+ @Configuration
+ @EnableKafka
+ public class KafkaConfig {
+ @Bean
+ public ConcurrentKafkaListenerContainerFactory<String, Object> kafkaListenerContainerFactory(
+ ConsumerFactory<String, Object> consumerFactory) {
+ ConcurrentKafkaListenerContainerFactory<String, Object> factory =
+ new ConcurrentKafkaListenerContainerFactory<>();
+ factory.setConsumerFactory(consumerFactory);
+ factory.setCommonErrorHandler(new DefaultErrorHandler());
+ return factory;
+ }
+ }
+ ```
- ### Orchestration-Based Saga
+ **Validation**: Enable transactional ID and verify exactly-once semantics.
- A **central orchestrator** manages the entire transaction flow and tells services what to do.
+ ### 6. Implement Saga Orchestrator (Orchestration Only)
- **Best for**: Brownfield applications, complex workflows, or when centralized control is needed
+ ```java
+ @Service
+ @RequiredArgsConstructor
+ public class OrderSagaOrchestrator {
+ private final KafkaTemplate<String, Object> kafka;
+ private final SagaStateRepository sagaStateRepo;
- **Advantages**:
- - Centralized visibility and monitoring
- - Easier to troubleshoot and maintain
- - Clear transaction flow
- - Simplified error handling
- - Better for complex workflows
+ public void startSaga(OrderRequest request) {
+ String sagaId = UUID.randomUUID().toString();
+ sagaStateRepo.save(new SagaState(sagaId, STARTED, LocalDateTime.now()));
+ kafka.send("saga.order.start", new StartOrderSagaCommand(sagaId, request));
+ }
- **Disadvantages**:
- - Orchestrator can become single point of failure
- - Additional infrastructure component
+ @KafkaListener(topics = "payment.failed")
+ public void handlePaymentFailed(PaymentFailedEvent event) {
+ kafka.send("order.compensate", new CompensateOrderCommand(event.getSagaId()));
+ kafka.send("inventory.compensate", new ReleaseInventoryCommand(event.getSagaId()));
+ sagaStateRepo.updateStatus(event.getSagaId(), FAILED);
+ }
+ }
+ ```
- ## Implementation Steps
+ **Validation**: Verify saga state persists before sending commands. Check compensation triggers on each failure path.
- ### Step 1: Define Transaction Flow
+ ### 7. Implement Event Handlers (Choreography Only)
- Identify the sequence of operations and corresponding compensating transactions:
+ ```java
+ @Service
+ public class OrderEventHandler {
+ private final OrderService orderService;
+ private final KafkaTemplate<String, Object> kafka;
- ```
- Order → Payment → Inventory → Shipment → Notification
- ↓ ↓ ↓ ↓ ↓
- Cancel Refund Release Cancel Cancel
+ @KafkaListener(topics = "payment.processed", groupId = "order-service")
+ public void onPaymentProcessed(PaymentProcessedEvent event) {
+ try {
+ InventoryReservedEvent result = orderService.reserveInventory(event.toInventoryRequest());
+ kafka.send("inventory.reserved", result);
+ } catch (InsufficientInventoryException e) {
+ kafka.send("inventory.insufficient", new InsufficientInventoryEvent(event.getOrderId(), event.getPaymentId()));
+ }
+ }
+ }
```
- ### Step 2: Choose Implementation Approach
-
- - **Choreography**: Spring Cloud Stream with Kafka or RabbitMQ
- - **Orchestration**: Axon Framework, Eventuate Tram, Camunda, or Apache Camel
-
- ### Step 3: Implement Services with Local Transactions
-
- Each service handles its local ACID transaction and publishes events or responds to commands.
+ **Validation**: Test that each event handler correctly triggers the next step or compensation.
- ### Step 4: Implement Compensating Transactions
+ ### 8. Add Monitoring and Observability
- Every forward transaction must have a corresponding compensating transaction. Ensure **idempotency** and **retryability**.
+ ```java
+ @Configuration
+ public class SagaMetricsConfig {
+ @Bean
+ public MeterRegistry meterRegistry() {
+ return new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
+ }
+ }
+ ```
- ### Step 5: Handle Failure Scenarios
+ Track: saga execution duration, compensation count, failure rate, stuck sagas.
- Implement retry logic, timeouts, and dead-letter queues for failed messages.
+ **Validation**: Set up alerts for sagas exceeding expected duration.
## Best Practices
- ### Design Principles
-
- 1. **Idempotency**: Ensure compensating transactions execute safely multiple times
- 2. **Retryability**: Design operations to handle retries without side effects
- 3. **Atomicity**: Each local transaction must be atomic within its service
- 4. **Isolation**: Handle concurrent saga executions properly
- 5. **Eventual Consistency**: Accept that data becomes consistent over time
-
- ### Service Design
-
- - Use **constructor injection** exclusively (never field injection)
- - Implement services as **stateless** components
- - Store saga state in persistent store (database or event store)
- - Use **immutable DTOs** (Java records preferred)
- - Separate domain logic from infrastructure concerns
-
- ### Error Handling
-
- - Implement **circuit breakers** for service calls
- - Use **dead-letter queues** for failed messages
- - Log all saga events for debugging and monitoring
- - Implement **timeout mechanisms** for long-running sagas
- - Design **semantic locks** to prevent concurrent updates
-
- ### Testing
-
- - Test happy path scenarios
- - Test each failure scenario and its compensation
- - Test concurrent saga executions
- - Test idempotency of compensating transactions
- - Use Testcontainers for integration testing
-
- ### Monitoring and Observability
-
- - Track saga execution status and duration
- - Monitor compensation transaction execution
- - Alert on stuck or failed sagas
- - Use distributed tracing (Spring Cloud Sleuth, Zipkin)
- - Implement health checks for saga coordinators
-
- ## Technology Stack
-
- **Spring Boot 3.x** with dependencies:
-
- **Messaging**: Spring Cloud Stream, Apache Kafka, RabbitMQ, Spring AMQP
-
- **Saga Frameworks**: Axon Framework (4.9.0), Eventuate Tram Sagas, Camunda, Apache Camel
-
- **Persistence**: Spring Data JPA, Event Sourcing (optional), Transactional Outbox Pattern
-
- **Monitoring**: Spring Boot Actuator, Micrometer, Distributed Tracing (Sleuth + Zipkin)
+ **Design**:
+ - Make compensating transactions **idempotent** using database constraints or deduplication tables
+ - Use **immutable events** (Java records) to prevent accidental mutation
+ - Store saga state in persistent storage for recovery
- ## Anti-Patterns to Avoid
+ **Error Handling**:
+ - Implement **circuit breakers** for inter-service calls
+ - Use **dead-letter queues** for messages exceeding retry limits
+ - Set appropriate **timeouts** per saga step (30s default, configurable)
- ❌ **Tight Coupling**: Services directly calling each other instead of using events
- ❌ **Missing Compensations**: Not implementing compensating transactions for every step
- ❌ **Non-Idempotent Operations**: Compensations that cannot be safely retried
- ❌ **Synchronous Sagas**: Waiting synchronously for each step (defeats the purpose)
- ❌ **Lost Messages**: Not handling message delivery failures
- ❌ **No Monitoring**: Running sagas without visibility into their status
- ❌ **Shared Database**: Using same database across multiple services
- ❌ **Ignoring Network Failures**: Not handling partial failures gracefully
+ **Monitoring**:
+ - Track saga status: PENDING, COMPLETED, COMPENSATING, FAILED
+ - Monitor compensation execution time
+ - Alert when sagas exceed SLA duration
## Constraints and Warnings
- - Every forward transaction MUST have a corresponding compensating transaction.
- - Compensating transactions MUST be idempotent to handle retry scenarios.
- - Saga state MUST be persisted to handle failures and recovery.
- - Never use synchronous communication between saga participants; it defeats the distributed nature.
- - Be aware that sagas provide eventual consistency, not strong consistency.
- - Monitor saga execution time and set appropriate timeouts to detect stuck sagas.
- - Test all failure scenarios including partial failures to ensure proper compensation.
- - Consider using a saga framework (Axon, Eventuate) for complex orchestrations to avoid reinventing the wheel.
- - Ensure message brokers are highly available to prevent saga interruption.
-
- ## When NOT to Use Saga Pattern
-
- Do not implement this pattern when:
-
- - Single service transactions (use local ACID transactions instead)
- - Strong consistency is required (consider monolith or shared database)
- - Simple CRUD operations without cross-service dependencies
- - Low transaction volume with simple flows
- - Team lacks experience with distributed systems
+ - Every forward transaction MUST have a corresponding compensating transaction
+ - Compensating transactions MUST be idempotent to handle retry scenarios
+ - Saga state MUST be persisted to handle failures and recovery
+ - Never use synchronous communication between saga participants
+ - Sagas provide eventual consistency, not strong consistency
+ - Test all failure scenarios including partial failures
+ - Consider Axon Framework or Eventuate for complex orchestrations
+ - Ensure message brokers are highly available
## Examples
- ### Input: Monolithic Transaction (Anti-Pattern)
+ ### Choreography-Based Saga
```java
- @Transactional
- public Order createOrder(OrderRequest request) {
- Order order = orderRepository.save(request);
- paymentService.charge(request.getPayment());
- inventoryService.reserve(request.getItems());
- shippingService.schedule(order);
- return order;
+ // Application.java
+ @SpringBootApplication
+ @EnableKafka
+ @EnableKafkaListeners
+ public class OrderApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(OrderApplication.class, args);
+ }
}
- ```
- ### Output: Saga-Based Distributed Transaction
+ // Event Classes (immutable)
+ public record OrderCreatedEvent(String orderId, List<OrderItem> items) {}
+ public record PaymentProcessedEvent(String paymentId, String orderId) {}
+ public record InventoryReservedEvent(String reservationId, String orderId) {}
+ public record PaymentFailedEvent(String orderId, String reason) {}
+ public record InsufficientInventoryEvent(String orderId, String paymentId) {}
- ```java
+ // OrderService with compensation
@Service
- public class OrderSagaOrchestrator {
- public OrderSummary createOrder(OrderRequest request) {
- // Step 1: Create order
- Order order = orderService.createOrder(request);
-
- try {
- // Step 2: Process payment
- Payment payment = paymentService.processPayment(
- new PaymentRequest(order.getId(), request.getAmount()));
-
- // Step 3: Reserve inventory
- InventoryReservation reservation = inventoryService.reserve(
- new InventoryRequest(order.getItems()));
-
- // Step 4: Schedule shipping
- Shipment shipment = shippingService.schedule(
- new ShipmentRequest(order.getId()));
-
- return OrderSummary.completed(order, payment, reservation, shipment);
+ @RequiredArgsConstructor
+ public class OrderService {
+ private final OrderRepository orderRepository;
+ private final KafkaTemplate<String, Object> kafka;
- } catch (PaymentFailedException e) {
- // Compensate: cancel order
- orderService.cancelOrder(order.getId());
- throw e;
- } catch (InsufficientInventoryException e) {
- // Compensate: refund payment, cancel order
- paymentService.refund(payment.getId());
- orderService.cancelOrder(order.getId());
- throw e;
- }
+ @KafkaListener(topics = "payment.failed", groupId = "order-service")
+ public void handleCompensation(PaymentFailedEvent event) {
+ orderRepository.findByOrderId(event.orderId())
+ .ifPresent(order -> {
+ order.setStatus(CANCELLED);
+ orderRepository.save(order);
+ });
}
}
```
- ### Input: Choreography Event Flow
-
- ```java
- // Event published when order is created
- @EventHandler
- public void on(OrderCreatedEvent event) {
- // Trigger payment processing
- paymentService.processPayment(event.getOrderId());
- }
- ```
-
- ### Output: Complete Choreography with Compensation
+ ### Orchestration-Based Saga with Axon Framework
```java
- @Service
- public class OrderEventHandler {
- @KafkaListener(topics = "order.created")
- public void handleOrderCreated(OrderCreatedEvent event) {
- try {
- paymentService.processPayment(event.toPaymentRequest());
- } catch (PaymentException e) {
- kafkaTemplate.send("order.payment.failed", new PaymentFailedEvent(event.getOrderId()));
- }
- }
- }
-
- @Service
- public class PaymentEventHandler {
- @KafkaListener(topics = "payment.processed")
- public void handlePaymentProcessed(PaymentProcessedEvent event) {
- inventoryService.reserve(event.toInventoryRequest());
- }
+ // Command
+ @Aggregate
+ public class OrderAggregate {
+ @AggregateIdentifier
+ private String orderId;
- @KafkaListener(topics = "payment.failed")
- public void handlePaymentFailed(PaymentFailedEvent event) {
- orderService.cancelOrder(event.getOrderId());
+ @CommandHandler
+ public OrderAggregate(CreateOrderCommand cmd) {
+ apply(new OrderCreatedEvent(cmd.orderId(), cmd.items()));
}
- }
- @Service
- public class InventoryEventHandler {
- @KafkaListener(topics = "inventory.reserved")
- public void handleInventoryReserved(InventoryReservedEvent event) {
- shippingService.scheduleShipment(event.toShipmentRequest());
+ @EventSourcingHandler
+ public void on(OrderCreatedEvent event) {
+ this.orderId = event.orderId();
}
- @KafkaListener(topics = "inventory.insufficient")
- public void handleInsufficientInventory(InsufficientInventoryEvent event) {
- // Compensate: refund payment
- paymentService.refund(event.getPaymentId());
- // Compensate: cancel order
- orderService.cancelOrder(event.getOrderId());
+ @CommandHandler
+ public void handle(CancelOrderCommand cmd) {
+ apply(new OrderCancelledEvent(cmd.orderId(), cmd.reason()));
}
}
```
- For detailed information, consult the following resources:
+ ## References
- [Saga Pattern Definition](references/saga-pattern-definition.md)
- - [Choreography-Based Implementation](references/choreography-implementation.md)
- - [Orchestration-Based Implementation](references/orchestration-implementation.md)
- - [Event-Driven Architecture](references/event-driven-architecture.md)
+ - [Choreography Implementation](references/choreography-implementation.md)
+ - [Orchestration Implementation](references/orchestration-implementation.md)
- [Compensating Transactions](references/compensating-transactions.md)
- [State Management](references/state-management.md)
- [Error Handling and Retry](references/error-handling-retry.md)
- [Testing Strategies](references/testing-strategies.md)
- - [Common Pitfalls and Solutions](references/pitfalls-solutions.md)
-
- See also [examples.md](references/examples.md) for complete implementation examples:
-
- - E-Commerce Order Processing (orchestration with Axon Framework)
- - Food Delivery Application (choreography with Kafka and Spring Cloud Stream)
- - Travel Booking System (complex orchestration with multiple compensations)
- - Banking Transfer System
- - Real-world microservices patterns
-
+ - [Pitfalls and Solutions](references/pitfalls-solutions.md)
+ - [Examples](references/examples.md)