Skip to main content

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

  1. No Distributed Transactions: Sagas provide eventual consistency without 2PC
  2. Compensating Actions: Every action must have a compensating action
  3. Idempotency: Steps must be idempotent for retry scenarios
  4. State Persistence: Saga state must be durable for recovery
ChoreographyOrchestration
Simple workflowsComplex workflows
Few servicesMany services
Loose couplingCentral control