Reliability › Reference Application

Reference application: checkout

A complete, runnable example of the reliability features working together across three services. Source: packages/laravel-kafka/tests/Reference/CheckoutApp.php (the services), CheckoutSagaTest.php (the scenarios).

cd packages/laravel-kafka
composer install
php vendor/bin/phpunit --filter CheckoutSagaTest

#What it models

                       ┌───────────────────────── Orders (owns the saga) ─────────────────────────┐
 POST /orders ────────▶│ Saga::start('order_checkout')                                             │
 (via the gateway)     │  1 CreateOrderRecord   ⟲ CancelOrder                                      │
                       │  2 ReserveStock        ⟲ ReleaseStock     ── inventory.reserve.requested ─┼──▶ Inventory
                       │       waits for inventory.stock.reserved   ◀── inventory.stock.reserved ──┼───   stock, reservations
                       │  3 ChargePayment       ⟲ RefundPayment    ── payments.charge.requested ───┼──▶ Payments
                       │       waits for payments.payment.completed ◀── payments.payment.completed ┼───   payments
                       │  4 ConfirmOrder                            ── orders.order.confirmed       │
                       └────────────────────────────────────────────────────────────────────────────┘
      ⟲ = compensation, run in reverse order when a later step fails, times out or is rejected

Every arrow is a Kafka event written to the sender's outbox in the same database transaction as its local change and read by the receiver through its inbox (one transaction: dedup record + local change + reply event).

#The scenarios (all in CheckoutSagaTest, all passing)

ScenarioProven outcome
Happy pathevents in exactly this order: created → reserve requested → reserved → charge requested → completed → confirmed; order confirmed, stock −2, one payment, saga completed
Payment declinedpayments.payment.failed → refund (no-op) → stock released → order cancelled; saga compensated; no payment row; cancelled event comes after the release
Out of stockorder cancelled; payments.charge.requested never emitted – no money is ever requested
Every message delivered twice to every consumerone reservation, stock decremented once, one payment, one saga; the inbox counter shows duplicates were actually seen by all three services
Payments crashes after its DB commit, before the offset committhe charge request is redelivered, skipped by the inbox, exactly one payment
Client starts the same checkout twiceone order, one charge, one stock decrement (idempotent Saga::start per correlation id + insertOrIgnore)
Broker outage loses the reply paththe step times out, the saga compensates, a late reserve request would be released again

To make sure these tests can actually fail, the inbox's duplicate check was disabled by hand: the duplicate-delivery and crash tests then fail with "2 payments instead of 1" (and pass again with the check restored).

#Mapping to a real deployment

#What this example does not show

Edit this page on GitHub