Saga Pattern in Flight, Hotel, Payment and Procurement Workflows
Why I needed Saga thinking
Travel booking is one of the easiest ways to understand distributed transactions.
A booking may involve:
create itinerary
|
reserve flight
|
reserve hotel
|
capture payment
|
confirm booking
|
notify travellerThe airline, hotel supplier and payment provider do not share one database transaction.
In procurement/RFx flows, the sequence is different but the problem is similar:
create RFx
-> publish
-> invite vendors
-> receive bids
-> evaluate
-> awardEach step may commit independently and may take minutes, hours or days.
I use the Saga pattern to make state, compensation and recovery explicit.
Why normal rollback does not work
Suppose:
Flight reserved YES
Hotel reserved YES
Payment captured NOThere is no ROLLBACK statement that undoes external supplier reservations.
We need business compensations:
payment failed
-> cancel hotel reservation
-> cancel flight reservation
-> mark booking FAILEDCompensation is not a database rollback. It is a new business operation and it can fail too.
Orchestrated Saga
For a complex booking flow I prefer orchestration because the current state and next action are explicit.
async execute(command: CreateBookingCommand) {
const saga = await this.sagaRepo.start(command.requestId);
try {
const flight = await this.flight.reserve(command.flight);
await this.sagaRepo.record(
saga.id,
'FLIGHT_RESERVED',
flight.id,
);
const hotel = await this.hotel.reserve(command.hotel);
await this.sagaRepo.record(
saga.id,
'HOTEL_RESERVED',
hotel.id,
);
const payment = await this.payment.capture(command.payment);
await this.sagaRepo.record(
saga.id,
'PAYMENT_CAPTURED',
payment.id,
);
await this.booking.confirm(command.bookingId);
await this.sagaRepo.complete(saga.id);
return { status: 'CONFIRMED' };
} catch (error) {
await this.compensate(saga.id);
throw error;
}
}The critical detail is persisted progress. If the orchestrator process dies after the hotel reservation, another instance must recover from stored state.
Compensation
async compensate(sagaId: string) {
const state = await this.sagaRepo.get(sagaId);
if (state.paymentCaptured && !state.paymentRefunded) {
await this.payment.refund(
state.paymentId,
sagaId,
);
}
if (state.hotelReserved && !state.hotelCancelled) {
await this.hotel.cancel(
state.hotelReservationId,
sagaId,
);
}
if (state.flightReserved && !state.flightCancelled) {
await this.flight.cancel(
state.flightReservationId,
sagaId,
);
}
await this.sagaRepo.markCompensated(sagaId);
}Every compensating action needs idempotency. The orchestrator may retry after a timeout without knowing whether the remote system already completed the cancellation.
Uncertain external result
This is one of the hardest real-world cases:
capture payment request sent
|
network timeout
|
Did provider capture or not?Retrying immediately can double-charge.
I introduce an intermediate state:
PAYMENT_STATUS_UNKNOWNThen perform reconciliation:
query provider by merchant reference
|
captured? yes/no
|
continue or compensateDistributed systems contain uncertainty. A good Saga model represents uncertainty instead of forcing every outcome into success/failure too early.
Choreography
For simple flows choreography can work:
BookingCreated
|
Flight service reserves
|
FlightReserved
|
Payment service captures
|
PaymentCapturedThe risk is that after many services participate, nobody can easily answer:
What is the complete workflow state for booking B1001?
For complex travel/procurement flows I normally prefer orchestration for the core business process and events for secondary reactions.
Saga + Outbox
When a Saga state transition must emit an event, I combine it with outbox:
DB transaction
|- update saga state
`- insert outbox event
outbox publisher
|
KafkaThis prevents a committed Saga state from silently failing to publish its next event.
Timeouts
A Saga needs business deadlines, not just HTTP timeouts.
Examples:
flight hold expires in 15 minutes
payment authorization expires
vendor bid window closes at 17:00
manual approval must finish in 24 hoursA scheduler or delayed event can transition timed-out Sagas into compensation or manual review.
Production states
I prefer explicit states such as:
STARTED
FLIGHT_RESERVED
HOTEL_RESERVED
PAYMENT_PENDING
PAYMENT_STATUS_UNKNOWN
CONFIRMED
COMPENSATING
COMPENSATION_FAILED
FAILED
MANUAL_REVIEWThese states are operationally valuable. Support teams can see what actually happened.
Failure handling
Compensation itself fails
Persist the failure, retry safely and alert operations. A Saga without manual recovery is incomplete.
Duplicate command
Use a stable Saga/business request ID.
Supplier sends late callback
Validate callback against the current Saga state and make state transitions conditional.
Process restarts halfway
Recover from persisted Saga state. Never rely only on in-memory workflow state.
Production checklist
- Persist every meaningful Saga state.
- Give each Saga a stable operation ID.
- Make steps and compensations idempotent.
- Model uncertain outcomes.
- Define business deadlines.
- Use reconciliation for external providers.
- Add a manual-recovery state.
- Trace the Saga across services.
- Combine state transitions with Outbox where required.
- Test process crash between every two steps.
FAQ
Orchestration or choreography?
For complex workflows I prefer orchestration because progression and recovery are visible. Choreography is useful for simpler independent reactions.
Is compensation guaranteed?
No. Compensation can fail and may require retry or manual intervention.
Can a Saga last days?
Yes. Procurement and approval workflows are often long-running.
Is Saga only for microservices?
No. It is useful whenever one business workflow spans independently committed systems.
Related architecture guides
- [Microservices Design Patterns I Have Used in Real Enterprise Projects](/articles/microservices-design-patterns-real-enterprise-projects)
- [Idempotency in Payments and Bookings](/articles/idempotency-payments-bookings-bids-event-consumers)
-
Adapter and Anti-Corruption Layer
Architect's final take
Saga is valuable because it admits the truth: distributed business transactions are sequences of independently committed actions. Recovery must be designed as carefully as the happy path.
Your feedback helps prioritize deeper technical content.






