pattern

Sagas in Practice

also called Compensating Transactions, Long-Running Transaction

Replacing a distributed transaction with a sequence of local ones and compensating actions, and the step ordering rule that determines whether it works.

sagacompensationconsistencyworkflow

Definition

A business transaction spanning multiple services, implemented as a sequence of local transactions where each step has a compensating action that semantically reverses it if a later step fails.

Why it matters

Because two-phase commit across independently deployed services is generally unavailable and undesirable: it requires a coordinator, holds locks across network calls, and blocks when the coordinator fails. Services with their own databases cannot participate in one anyway.

The conceptual shift is that you cannot roll back a committed local transaction elsewhere, so you issue a new business action that reverses it. A payment is not un-taken; it is refunded.

Implementation patterns

Orchestration. A coordinator calls each service in turn, holds the process state, and decides what happens on failure. The flow is readable in one place, the current state of any instance is queryable, compensation is centralised, and changing the sequence touches one component.

Choreography. Each service publishes events and reacts to others', with no central authority. Loosely coupled, and the process exists nowhere — understanding it requires reading every service, and answering "why is this order stuck" means correlating events across many logs.

The heuristic: orchestrate business processes, choreograph notifications. A flow with defined steps, a completion condition and an owner who is asked about its status wants an orchestrator.

Failure scenarios

Non-compensable steps ordered too early. This is the decisive design rule. A dispatched physical shipment cannot be un-shipped; a sent email cannot be unsent. Order steps so non-compensable actions come last, after everything reversible has succeeded.

Compensation that itself fails, which needs escalation to a human rather than infinite retry.

Non-idempotent steps or compensations, both of which will be retried.

Lost saga state on orchestrator crash, so a half-completed transaction is abandoned. State must be durable.

No operator visibility. Some sagas will get stuck, and without an interface to inspect and intervene, the resolution is a developer writing database updates during an incident.

Intermediate states visible to users, since compensation is not invisible — a charge and a refund both appear on a statement, and the business must accept that.

Industry example

The canonical e-commerce saga makes the ordering rule concrete: reserve inventory, authorise payment, dispatch, confirm. Reservation and authorisation are holds — reversible cheaply, and an abandoned saga expires naturally. Capture and dispatch are the irreversible steps and belong at the end. Moving payment capture from order placement to dispatch aligns the money with the goods and makes cancellation before dispatch clean.

Trade-offs

Sagas give cross-service consistency without distributed transactions, at the cost of eventual consistency with user-visible intermediate states, compensation logic for every step, and a workflow engine to build or adopt.

The alternative worth considering first: can the operation be confined to one service and one transaction? A saga spanning four services frequently indicates boundaries drawn in the wrong place.

Interview question

Design an order saga: reserve inventory, take payment, dispatch, send confirmation. What ordering problem do you see?

The expected observation is that dispatch and confirmation are non-compensable and must come last. Strong answers add reservations rather than commitments for scarce resources, authorise-then-capture for payment, idempotency on every step and compensation, durable saga state, and an operator interface for stuck instances.