System design · application
Design: E-commerce Checkout
One "place order" click spans four independent services — orchestrating it safely takes a saga, a strategy, a breaker, and a queue working together.
One order, four services, four separate databases
Reserving stock, charging a card, creating the order, and emailing a receipt each live in a different service. A naive sequential call chain has no way to undo itself when step 3 fails.
async function checkout(cart) {
// naive: no atomicity, no rollback
await inventory.reserve(cart);
await payment.charge(cart); // fails here?
await orders.create(cart); // stock stays reserved forever!
await email.send(cart); // blocks the response
}
The 10,000-ft view — one order, several services
Before the details: the whole checkout in one glance. Follow the numbers; if a step fails, the earlier ones are undone.
An orchestrator runs the checkout as a Saga
A Saga orchestrates Reserve → Charge → Create order, then hands off to a queue for async email. The payment provider is chosen via a Strategy (Card / PayPal); a circuit breaker guards the flaky gateway. Catalog reads use CQRS/cache so browsing never touches the write path.
class CheckoutSaga {
run(order) {
inventory.reserve(order); // 1
try {
gateway.charge(order); // Strategy+CB
} catch (e) {
inventory.release(order); // compensate
return order.fail();
}
orders.create(order); // 3
queue.publish('email.send'); // async
}
}
Run the saga — forward, or with compensation
Place an order and watch each saga step fire, in order. Force a payment failure to see the compensation (release inventory) fire instead of a crash. Switch the payment Strategy — the saga code never changes.
Follow the numbered messages top-to-bottom — the Orchestrator drives every step; a declined charge triggers a compensating release instead of an order.
Every earlier lesson, one checkout
- Saga + compensation drives the distributed transaction — no two-phase commit across four databases needed.
- Strategy swaps payment providers (Card/PayPal) without touching the saga's control flow.
- Circuit breaker wraps the flaky payment gateway so one slow provider can't cascade into a full outage.
- Queue + DLX delivers the confirmation email asynchronously and retries (or parks) failures without blocking checkout.
- CQRS/cache serves catalog browsing off the write path entirely.
- Each saga step — reserve, charge, create, publish — is itself a Command: an object the orchestrator can invoke, retry, or undo uniformly.