The Hidden Challenge of Microservices: Unraveling the Saga Pattern with Spring Boot
Edmar Fagundes
Senior Java Software Engineer | Kotlin | Spring Boot | React | Angular | AWS | Docker | Kubernetes | TypeScript | FullStack
Challenges in Microservices Transactions
In a microservices architecture, a single business operation often requires interactions across multiple services. Unlike monolithic applications, microservices do not share a single database, making traditional ACID transactions impractical. If an error occurs in one of these microservices, the rollback of the local transaction is not propagated to other microservices, leading to inconsistent states in the system.
For example, in an e-commerce system, when a user places an order:
If the Inventory Service fails due to insufficient stock, the Payment Service has already charged the customer, and there’s no automatic rollback across services. This creates a major challenge in ensuring data consistency.
Introducing the Saga Pattern
The Saga Pattern is a mechanism to handle distributed transactions by breaking them into a sequence of local transactions. If a step fails, compensating transactions are executed to undo the previous steps, ensuring the system remains consistent.
There are two main Saga models:
1?? Orchestration-Based Saga (Centralized Control)
In this approach, a central Saga Orchestrator manages the workflow, calling each microservice in sequence. If any step fails, the orchestrator triggers compensating transactions in the reverse order.
Pros and Cons of Orchestration-Based Saga
? Pros:
? Cons:
?? Example: Implementing Orchestrated Saga in Spring Boot
@Service
public class OrderSagaOrchestrator {
@Autowired private OrderService orderService;
@Autowired private PaymentService paymentService;
@Autowired private InventoryService inventoryService;
public void createOrder(OrderRequest request) {
try {
Order order = orderService.createOrder(request);
paymentService.processPayment(order);
inventoryService.reserveStock(order);
} catch (Exception e) {
// If any service fails, trigger compensation
compensateOrder(request);
}
}
private void compensateOrder(OrderRequest request) {
paymentService.refundPayment(request.getOrderId());
orderService.cancelOrder(request.getOrderId());
}
}
?? If the inventory service fails, the orchestrator will refund the payment and cancel the order.
2?? Choreography-Based Saga (Decentralized Events)
Instead of a central controller, each microservice listens for events and reacts accordingly.
Pros and Cons of Choreography-Based Saga
? Pros:
? Cons:
领英推荐
?? Example: Implementing Choreography Saga in Spring Boot
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
applicationEventPublisher.publishEvent(new PaymentInitiatedEvent(event.getOrderId()));
}
The Core of Saga: Compensating Transactions
What is a Compensating Transaction?
A compensating transaction is an operation that undoes the effect of a previously executed transaction in a distributed system. Since microservices do not support global rollbacks, compensating transactions are the only way to ensure eventual consistency across services.
For example, if an order is placed but the payment fails, a compensating transaction should cancel the order and release any reserved stock.
Why is Compensating Transactions the Core of Saga?
Compensating transactions are the core of the Saga Pattern because they replace the traditional rollback mechanism used in monolithic databases. They enable a controlled and reversible process for handling failures in a distributed system. Without them, failed operations would leave the system in an inconsistent state, causing incorrect payments, lost orders, or stock discrepancies.
Implementing Compensating Transactions in Spring Boot
@Service
public class PaymentService {
@Transactional
public void processPayment(Long orderId) {
Payment payment = new Payment(orderId, "COMPLETED");
paymentRepository.save(payment);
}
@Transactional
public void refundPayment(Long orderId) {
Payment payment = paymentRepository.findByOrderId(orderId);
if (payment != null && "COMPLETED".equals(payment.getStatus())) {
payment.setStatus("REFUNDED");
paymentRepository.save(payment);
}
}
}
Handling Compensating Transaction Failures
One common mistake in implementing the Saga Pattern is forgetting that compensating transactions can also fail. If this failure is not handled properly, the system could be left in an inconsistent state, potentially causing financial loss, incorrect data, or resource lock issues for the company.
To mitigate these risks, different strategies must be applied to ensure compensating transactions are properly retried, logged, or escalated for manual intervention when necessary.
1?? Retry Mechanism
Retries help recover from temporary failures (e.g., network timeouts, database locks). They can be implemented in two ways:
@Retryable(value = Exception.class, maxAttempts = 3, backoff = @Backoff(delay = 2000))
public void refundPayment(Long orderId) {
paymentService.refund(orderId);
}
2?? Dead Letter Queue (DLQ)
If compensating transactions fail after multiple retries, they should be moved to a Dead Letter Queue (DLQ) for further investigation.
Example of DLQ Handling in Kafka
@KafkaListener(topics = "compensate-payment")
public void handleCompensatingTransaction(ConsumerRecord<String, String> record) {
try {
paymentService.refundPayment(record.value());
} catch (Exception e) {
kafkaTemplate.send("compensate-payment-dlq", record.value());
}
}
3?? Manual Intervention
Some issues cannot be resolved automatically and require human intervention:
A proper monitoring and alerting system should be in place to notify engineers and business operators when intervention is needed.
Conclusion
By applying these strategies in Spring Boot, we can build resilient microservices capable of handling failures effectively and ensuring data consistency even in distributed environments.
Senior .NET Software Engineer | Senior Full Stack Developer | C# | .Net Framework | Azure | React | SQL | Microservices
2 周Great example, makes sense!
Senior Full-Stack Software Engineer | Back-end | .Net | Azure | GCP | React | Angular
2 周Dicas úteis
Senior Business Analyst | ITIL | Communication | Problem-Solving | Critical Thinking | Data Analysis and Visualization | Documentation | BPM | Time Management | Agile | Jira | Requirements Gathering | Scrum
2 周Very informative! Thanks for sharing Edmar Fagundes ! ????
Software Engineer | React | Node
2 周Very informative