A stay booking spans payment authorisation, calendar reservation, host notification and a guest confirmation email. A distributed ACID transaction is not available. How do you order the steps and what do you do when a compensation fails?
Show the full answer Hide the answer
What is being tested
Whether you order steps by reversibility rather than by convenience, and whether you have a plan for the case everyone forgets: the compensation itself failing.
Ordering by reversibility
Rank each step by how cleanly its business effect can be undone:
| Step | Reversibility | Position |
|---|---|---|
| Validate and price | Trivial - no effect | First |
| Reserve calendar dates | Clean - release the hold | Early |
| Authorise payment | Clean - void the authorisation | Early |
| Capture payment | Harder - refund with fees and delay | Late |
| Notify the host | Impossible - they have read it | Late |
| Send guest confirmation | Impossible | Last |
The rule: irreversible steps last. If the confirmation email goes out before the calendar reservation is confirmed, you have told a guest they have a booking that may not exist, and no amount of engineering recovers that.
Authorise early and capture late is the standard payments shape for exactly this reason: it holds the money without moving it, and voiding a hold is clean where refunding a capture is not.
The calendar step deserves special care
This is the step with a hard invariant — two guests must not hold the same night — and it is the one place where a real transaction is both available and necessary, because the calendar lives in one service with one database. Take a conditional write on the date range with the current version, so two concurrent bookings cannot both succeed.
Note the general shape: search is eventually consistent and cheap; the commit is strongly consistent and authoritative. Do not try to make search strongly consistent, and never let the commit trust search.
When a compensation fails
This is the question that separates a real design from a diagram. The answer has four parts:
- Retry the compensation with backoff, a bounded number of times. Most failures are transient.
- Make compensations idempotent, because those retries will duplicate.
- Define a terminal state — something like
COMPENSATION_FAILED— which is a real state in the state machine, not an exception. The saga stops and stays inspectable. - Alert a human, with context. Sagas do not eliminate manual intervention; they bound it, make it visible, and hand the operator everything they need to resolve it.
A system that has no answer for step 3 will silently leave entities in half-completed states, and the first anyone hears of it is a customer complaint weeks later.
Two more failure modes worth naming
Concurrent sagas over the same entity. Two cancellations arrive at once. Without a state machine that rejects invalid transitions, both compensate and the guest is refunded twice. The state machine is the lock.
Semantic locks. While the saga is in flight the booking is in PENDING, and that state must be
visible to everything else, so no other process treats it as final.
Orchestration or choreography
For four steps with compensations and a support team that needs to answer "where is booking 41,229 stuck?", orchestration. The flow is explicit, inspectable and testable. Choreography spreads the flow across services so it exists nowhere, which is tolerable at three steps and unmanageable at eight.