design·lab

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.

✗ The challenge

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
}
📦 Inventory
own DB
💳 Payment
own DB
🧾 Orders
own DB
📧 Email
own queue
Must not oversell, must not charge without a stock reservation, must survive partial failures, must support multiple payment providers, and confirmation email must be async — no single database transaction can span all four services.
✓ Big picture

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.

✓ The architecture

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
  }
}
🎬 Orchestrator
runs the Saga
📦 Inventory
reserve
💳 Payment
Strategy + breaker
🧾 Orders
create
📬 Queue
→ Email (async)
🗂️ Catalog reads
CQRS + cache
✓ See it live

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.

💳 Card 🅿️ PayPal

Follow the numbered messages top-to-bottom — the Orchestrator drives every step; a declined charge triggers a compensating release instead of an order.

✓ How it combines

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.
🎯 Combines everything: Saga + Strategy + Circuit Breaker + Queue/DLX + CQRS — principles (SRP, DIP, Open/Closed) holding each piece together. This is the whole site in one design.