PromptForge Academy
saga-patterndistributed-systemsmicroservicesreliability

Saga Pattern Explained: Managing Distributed Transactions with AI

Once a business process spans multiple services, there's no database transaction big enough to cover it. The saga pattern — and its hardest part, writing compensations — explained with a worked AI prompt.

"Place an order" might mean: reserve inventory, charge payment, schedule shipping — three services, three separate databases, no single transaction that spans all of them. If shipping scheduling fails after payment already succeeded, there's no ROLLBACK that undoes the charge. The saga pattern is how you handle this: break the process into a sequence of local transactions, each with an explicit compensating action to undo it if a later step fails.

Choreography vs. orchestration

StyleHow it worksTrade-off
ChoreographyEach service publishes an event when its step completes; the next service reacts to that event independently. No central coordinator.No single point of failure, but the overall process flow is implicit — spread across every service's event handlers, harder to see end-to-end
OrchestrationA central saga orchestrator explicitly calls each step in sequence and decides what to do on failure.The process flow is explicit and easy to trace in one place, but the orchestrator becomes a critical dependency and a potential bottleneck

Choreography scales better for a small number of steps with loose coupling; orchestration is usually easier to reason about and debug once a saga has more than 3-4 steps, because the alternative is reconstructing the flow from scattered event handlers across services.

The part everyone underestimates: compensations

A compensation is not a rollback

You can't undo a payment charge the way a database undoes an uncommitted write — you issue a refund, which is a new operation with its own failure modes. Every compensating action needs to be designed as carefully as the forward action it's undoing, including what happens if the compensation itself fails.

Forward stepCompensating actionWhat can go wrong with the compensation
Reserve inventoryRelease reservationReservation already expired/consumed — releasing a nonexistent reservation must be a safe no-op
Charge paymentIssue refundRefund itself can fail or be delayed — needs its own retry and eventual-consistency handling
Schedule shipmentCancel shipmentShipment may already be in a state (picked, packed) where cancellation has its own cost/process

Worked example

Prompt: design a saga with explicit compensations
New Project Advanced — Distributed Systems module
Context: Order placement spans three services: Inventory, Payment, Shipping. Business rule: an order is only confirmed if all three steps succeed; any failure must fully unwind prior successful steps.

Task: Design this as a choreography-based saga.

For each step, specify:
1. The event that triggers it.
2. The event it publishes on success.
3. The event it publishes on failure.
4. Its compensating action, and what event triggers that compensation.

Constraints:
- Explicitly state what happens if a compensation itself fails (e.g. refund service is down) — do not leave this unhandled.
- Identify which steps are NOT safely compensable at all (e.g. an email already sent) and how the design accounts for that.
- State the idempotency requirement for each step, since events may be delivered more than once.

Output format: a table of steps, then a written failure-scenario walkthrough for "payment succeeds, shipping fails."

When you don't need a saga at all

If the entire process can live inside one service and one database transaction, use that — a saga's eventual-consistency and compensation complexity is a cost you pay only when the process genuinely spans transactional boundaries. The most common mistake is reaching for saga machinery inside what's actually a single-service operation that got split into microservices prematurely.

This pairs directly with the transactional outbox pattern — each saga step publishing its completion event reliably depends on outbox-style atomic writes, not a best-effort publish after the local transaction commits.