System DesignMedium
What is the saga pattern and when should you use it?
Saga is a sequence of local transactions across services, each one accompanied by a compensating action that reverses it. Used when you can't run a distributed transaction.
Example: order placement spans Order, Payment, Inventory, Shipping services.
// Choreography flavour — each service reacts to events
public class OrderPlacedHandler {
public async Task HandleAsync(OrderPlaced evt) {
try {
await payment.ChargeAsync(evt.OrderId, evt.Amount);
} catch {
await orders.MarkFailedAsync(evt.OrderId); // compensating action
}
}
}
Orchestration flavour — a saga orchestrator drives the sequence:
public class PlaceOrderSaga {
public async Task ExecuteAsync(PlaceOrderCommand cmd) {
try {
await payment.AuthorizeAsync(cmd.OrderId, cmd.Amount);
await inventory.ReserveAsync(cmd.OrderId, cmd.Items);
await shipping.ScheduleAsync(cmd.OrderId, cmd.Address);
await orders.MarkConfirmedAsync(cmd.OrderId);
} catch (Exception) {
// run compensations in reverse order
await shipping.CancelAsync(cmd.OrderId);
await inventory.ReleaseAsync(cmd.OrderId);
await payment.VoidAsync(cmd.OrderId);
await orders.MarkFailedAsync(cmd.OrderId);
throw;
}
}
}
When to use saga:
- You're firmly in microservices with separate data stores
- A business workflow truly spans services (placing an order, refunding, onboarding)
- You can model compensation actions that are safe to retry (idempotent)
When NOT to use saga:
- Within a single bounded context — use a regular DB transaction
- When eventual consistency leaks into the UX painfully ("I see my refund but my balance still shows old amount")
- When compensation is impossible (you sent the package — there's no "unsend")
The hard parts nobody warns you about:
- Idempotency keys on every step (network retries duplicate events)
- Saga state must be persisted (in-memory state dies with the process)
- Timeouts and dead-letter handling — what happens when payment.Authorize hangs?
Most teams underestimate the operational overhead. Start with a modular monolith and well-defined transactions; reach for sagas when distribution is non-negotiable.