Learn Saga Design Pattern to handle distributed transactions
The Saga design pattern is used to handle distributed transactions in a microservices architecture. It breaks a single business process that spans multiple microservices into a series of local transactions. Each local transaction updates the state of a microservice and publishes an event to trigger the next transaction in the saga. If a local transaction fails, the saga executes compensating transactions to undo the changes made by the preceding transactions.
Types of Sagas
- Choreography-Based Saga: Each microservice involved in the saga publishes events and listens to the events of others to determine when to proceed.
- Orchestration-Based Saga: A centralized coordinator (SAGA Execution
Coordinator (SEC) or Orchestrator) manages
the saga by invoking and coordinating the microservices.
The SEC is an additional component responsible for controlling the events between the
services participating in the SAGA.
Here are a few frameworks available to implement the orchestrator pattern:
- Camunda is a Java-based framework that supports Business Process Model and Notation (BPMN) standard for workflow and process automation.
- Apache Camel provides the implementation for Saga Enterprise Integration Pattern (EIP).
Architecture- Choreography Based Saga using Event Driven
Architecture- Orchestration Based Saga using Event Driven
Architecture- Orchestration Based Saga using Direct Synchronous Calls
Implementation of Choreography-Based Saga using Event Driven
In a Choreography-based saga, microservices communicate through events. Here’s a basic example involving three microservices: Order Service, Payment Service, and Inventory Service.
Steps:
- Order Service: Creates an order and emits an
OrderCreatedevent. - Payment Service: Listens to
OrderCreatedevent, processes the payment, and emits aPaymentProcessedevent. - Inventory Service: Listens to
PaymentProcessedevent, reserves inventory, and emits anInventoryReservedevent. - Order Service: Listens to
InventoryReservedevent and completes the order.
Code Example:
Order Service
public class OrderService {
private EventBus eventBus;
public void createOrder(Order order) {
// Save order to database
eventBus.publish(new OrderCreatedEvent(order.getId()));
}
@EventListener
public void onInventoryReserved(InventoryReservedEvent event) {
// Complete the order
Order order = orderRepository.findById(event.getOrderId());
order.setStatus(OrderStatus.COMPLETED);
orderRepository.save(order);
}
}
Payment Service
public class PaymentService {
private EventBus eventBus;
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// Process payment
boolean success = processPayment(event.getOrderId());
if (success) {
eventBus.publish(new PaymentProcessedEvent(event.getOrderId()));
}
}
}
Inventory Service
public class InventoryService {
private EventBus eventBus;
@EventListener
public void onPaymentProcessed(PaymentProcessedEvent event) {
// Reserve inventory
boolean success = reserveInventory(event.getOrderId());
if (success) {
eventBus.publish(new InventoryReservedEvent(event.getOrderId()));
}
}
}
Implementation of Orchestration-Based Saga using Direct Synchronous Calls
In an Orchestration-based saga, a central orchestrator coordinates the saga. Here’s an example involving the same three microservices: Order Service, Payment Service, and Inventory Service.
Steps:
- Orchestrator: Invokes the Order Service to create order.
- Order Service: Creates an order and notifies the Orchestrator.
- Orchestrator: Invokes the Payment Service to process the payment.
- Payment Service: Processes the payment and notifies the Orchestrator.
- Orchestrator: Invokes the Inventory Service to reserve inventory.
- Inventory Service: Reserves inventory and notifies the Orchestrator.
- Orchestrator: Completes the order by notifying the Order Service.
Code Example:
Orchestrator
public class OrderSagaOrchestrator {
private OrderService orderService;
private PaymentService paymentService;
private InventoryService inventoryService;
public void createOrder(Order order) {
orderService.createOrder(order);
handlePayment(order.getId());
}
private void handlePayment(String orderId) {
boolean paymentSuccess = paymentService.processPayment(orderId);
if (paymentSuccess) {
handleInventory(orderId);
} else {
orderService.cancelOrder(orderId);
}
}
private void handleInventory(String orderId) {
boolean inventorySuccess = inventoryService.reserveInventory(orderId);
if (inventorySuccess) {
orderService.completeOrder(orderId);
} else {
paymentService.refundPayment(orderId);
orderService.cancelOrder(orderId);
}
}
}
Order Service
public class OrderService {
public void createOrder(Order order) {
// Save order to database
}
public void completeOrder(String orderId) {
Order order = orderRepository.findById(orderId);
order.setStatus(OrderStatus.COMPLETED);
orderRepository.save(order);
}
public void cancelOrder(String orderId) {
Order order = orderRepository.findById(orderId);
order.setStatus(OrderStatus.CANCELED);
orderRepository.save(order);
}
}
Payment Service
public class PaymentService {
public boolean processPayment(String orderId) {
// Process payment
return true; // Return true if payment is successful
}
public void refundPayment(String orderId) {
// Refund payment
}
}
Inventory Service
public class InventoryService {
public boolean reserveInventory(String orderId) {
// Reserve inventory
return true; // Return true if inventory reservation is successful
}
}
Conclusion
The Saga pattern provides a way to handle distributed transactions in microservices by breaking down a transaction into smaller steps and ensuring consistency through either choreography or orchestration. The choice between choreography and orchestration depends on the specific use case, complexity, and preferences of the development team.