Portal Community

The Saga Problem

A distributed business transaction spans multiple independent workflows (e.g., reserve inventory, charge payment, trigger shipment). If the payment fails after inventory is reserved, the reservation must be reversed. This is the saga compensation pattern.

Pattern Structure


Orchestrator Workflow
    │
    ├── StartWorkflow (reserveInventory)    ──→ executionId: inv-exec-001
    │
    ├── StartWorkflow (chargePayment)       ──→ executionId: pay-exec-002
    │       │
    │       └── [error] ──→ CompensationPath:
    │                           CancelWorkflow (inv-exec-001)         // cancel inventory reservation
    │                           StartWorkflow (reversePaymentAttempt) // if partially charged
    │                           SendEmail (failure notification)
    │                           EndNode (failed)
    │
    └── StartWorkflow (triggerShipment)
            │
            └── [all succeeded] → SendEmail (success confirmation) → EndNode (completed)
  

Key Design Rules

RuleWhy
Record executionId of every child workflow immediately after startingYou need it to cancel/compensate later
Each child workflow should be idempotentCancellation signals may arrive after partial completion
Compensation workflows should not failA failed compensation is worse than the original failure — use retries and alerting
Store executionIds in an entity recordIf the orchestrator itself fails, the execution IDs survive in persistent storage for manual recovery
No distributed ACID: Sagas do not provide ACID guarantees across workflows. Compensation logic must handle partial states (e.g., inventory reserved but payment partially processed). Design each step to be reversible and test compensation paths explicitly.