Saga Pattern
The Saga pattern manages data consistency across microservices without distributed ACID transactions.
The Problem
Saga Types
Choreography-Based
Services communicate through events without central coordination:
// Order Service publishes event
await _eventBus.PublishAsync(new OrderCreatedEvent { OrderId = order.Id });
// Payment Service reacts
public async Task HandleAsync(OrderCreatedEvent @event)
{
var payment = await _paymentGateway.ProcessAsync(@event);
await _eventBus.PublishAsync(new PaymentCompletedEvent { OrderId = @event.OrderId });
}
// Inventory Service reacts to payment
public async Task HandleAsync(PaymentCompletedEvent @event)
{
await _inventory.ReserveAsync(@event.OrderId);
}
Orchestration-Based
Central coordinator controls the flow:
public async Task ExecuteAsync(Guid orderId)
{
try
{
await _paymentService.ProcessPaymentAsync(orderId);
await _inventoryService.ReserveStockAsync(orderId);
await _shippingService.ScheduleShipmentAsync(orderId);
}
catch (Exception)
{
await CompensateAsync(); // Undo all successful steps
}
}
Compensating Transactions
private async Task CompensateAsync(OrderSagaState saga)
{
// Compensate in REVERSE order
if (saga.ShipmentScheduled)
await _shippingService.CancelShipmentAsync(saga.ShipmentId);
if (saga.InventoryReserved)
await _inventoryService.ReleaseStockAsync(saga.OrderId);
if (saga.PaymentCompleted)
await _paymentService.RefundPaymentAsync(saga.PaymentId);
}
Key Takeaways
- No Distributed Transactions: Sagas provide eventual consistency without 2PC
- Compensating Actions: Every action must have a compensating action
- Idempotency: Steps must be idempotent for retry scenarios
- State Persistence: Saga state must be durable for recovery
| Choreography | Orchestration |
|---|---|
| Simple workflows | Complex workflows |
| Few services | Many services |
| Loose coupling | Central control |