advanced 2 min answer

Placing an order must reserve stock, charge the card and create a shipment across three services. Design it, and justify why not a distributed transaction.

sagatransactionspivotcompensation
Show the full answer Hide the answer

First: question the boundary

A transaction spanning three services often means one invariant has been split across three owners. Before designing a protocol, check whether stock reservation and order creation genuinely belong to different services — if they change together and fail together, they may be one service with a network call inside it.

Assuming the boundaries are right (different teams, different scaling profiles, shipment genuinely external), proceed.

Why not two-phase commit

The coordinator becomes a single point of failure whose crash after prepare leaves participants blocked holding locks — across three services, for an unbounded time. Locks are held across the network for the whole protocol, which destroys throughput. And the payment provider is a third party that will not participate in your XA transaction under any circumstances.

The design: a saga with a deliberate pivot

Step order matters more than anything else here, and it is chosen so the irreversible step is as late as possible:

  1. Reserve stock — compensatable (release the reservation). Cheap to undo, no customer-visible effect.
  2. Authorise payment — compensatable (void the authorisation). An authorisation is not a capture; voiding it leaves no charge on the statement, which is materially better than a refund.
  3. Create the order — the pivot. After this the customer has an order.
  4. Capture payment — retriable. Must eventually succeed.
  5. Create the shipment — retriable.
  6. Send confirmation — retriable, and last because an email cannot be un-sent.

Authorise-then-capture rather than a single charge is the key detail: it moves the irreversible money movement after the pivot, so any failure before step 3 is invisible to the customer.

The mechanics

An orchestrator, not choreography, because the flow needs to be readable, needs timeouts, and needs an operator view of stuck instances. A durable workflow engine or an explicit state machine with persisted state.

An outbox for every event emitted, so publishing is atomic with the local state change.

Idempotency keys on every call, since every step will be retried.

Explicit intermediate statesreserved, authorising, pending — so other processes reading the order know not to rely on it. Without these, the compensation cannot fully undo what a reader did in the meantime.

Reservation expiry, so an abandoned saga releases stock without intervention.

What must be decided with the business

What happens if capture fails after the pivot. The order exists and the money did not move — that is a business exception requiring a policy (retry window, then cancel with notification, then escalate), not a technical decision. Sagas surface these questions; they do not remove them.

What a strong answer adds

Naming the reservation-first design (TCC) as the alternative worth considering: if every participant can hold a reservation, nothing is visible until a confirm phase and cancellation is always clean. It costs three idempotent operations per participant, and it fits this domain — stock, payments and shipping all naturally support reservations.