Patterns · Multi-step processes

Patterns

Multi-step processes

Sagas and workflows: how to finish a business process that spans several services without pretending you have a distributed transaction.

2 min read system-design

A checkout charges a card, reserves stock, creates an order, and emails a receipt. Those steps do not share a transaction. A grading pipeline transcribes, scores, and asks a human. A crash in the middle is normal. A saga is the record of how far you got and what you will undo.

The state

One row (or a workflow engine’s history) holds the process id, the current step, and the ids of external effects you have already created (payment_id, reservation_id). Steps advance only by committing that row. You resume by reading it. If the state lives only in a process’s memory, a deploy is data loss.

Write the transition and an outbox event in the same database transaction. A dispatcher delivers the command to the next worker. That way you never “charged the card” in memory and forgot to tell anyone.

Forward and back

Each step is idempotent, keyed by the process id. Running “reserve stock” twice reserves once.

If step N fails for good, run compensations for N-1, N-2, … down to the start. A compensation is another step: refund this payment, release this reservation. It has its own idempotency key. Some steps cannot be compensated (an email already sent). Say so, and send a correction rather than pretending you rolled back.

Timeouts are steps. If a human does not answer in two days, the workflow takes the timeout branch. Without that branch the table fills with zombies.

Orchestrator or events

An orchestrator (a service, or Temporal or a similar engine) makes the sequence readable and gives you timers, retries, and a UI for “what is stuck?” Choreography, where each service listens for events and emits more, fits when no team owns the whole process. It is harder to debug. For an interview, draw the orchestrator unless the company is clearly an event-driven platform. Name Temporal if you want a real engine; do not spend the interview re-deriving it.

Do not hold a database lock while you wait for a card network or a person. Record the intent, commit, and continue when the reply arrives.

stateDiagram-v2
  [*] --> Reserved
  Reserved --> Charged
  Charged --> Confirmed
  Charged --> Refunding: create order failed
  Refunding --> Failed
  Confirmed --> [*]

Further reading

Check yourself

Answer the questions. Multiple choice is scored on this device only.

1. Why not use two-phase commit across the payment provider and your database?

2. A compensation must be:

3. Orchestration versus choreography?