E-Commerce Order State Machine in TLA+
Table of Contents
Overview
This specification models the state transitions in a typical e-commerce system (inspired by the OpenAPI PetStore example). We explore how Cart, Order, Shipment, and Status can become misaligned in a distributed architecture.
The Problem: State Synchronization
In microservices architectures, entities often span multiple services:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Cart │───►│ Order │───►│ Shipment │───►│ Status │
│ Service │ │ Service │ │ Service │ │ Service │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
│ │ │ │
▼ ▼ ▼ ▼
CartDB OrderDB ShipmentDB StatusDB
Each service maintains its own state, leading to potential inconsistencies:
- Cart shows items, but order failed to create
- Order exists, but shipment never created
- Shipment delivered, but status shows "processing"
State Definitions
Cart States
---- MODULE ECommerceStates ---- EXTENDS Integers, Sequences, FiniteSets CONSTANTS Items, MaxQuantity \* Cart states CartStates == {"empty", "active", "abandoned", "converted"} \* Order states OrderStates == {"pending", "confirmed", "paid", "cancelled", "refunded"} \* Shipment states ShipmentStates == {"none", "preparing", "shipped", "in_transit", "delivered", "returned"} \* Status (customer-facing aggregated state) StatusStates == {"browsing", "checkout", "processing", "shipped", "completed", "failed"}
Variables
VARIABLES cart, \* Current cart state order, \* Current order state shipment, \* Current shipment state status, \* Customer-facing status items, \* Items in cart/order history \* Audit trail of state changes vars == <<cart, order, shipment, status, items, history>>
State Transitions
Cart Actions
\* Add item to cart AddToCart(item) == /\ cart \in {"empty", "active"} /\ items' = items \union {item} /\ cart' = "active" /\ status' = "browsing" /\ UNCHANGED <<order, shipment>> /\ history' = Append(history, <<"AddToCart", item>>) \* Remove item from cart RemoveFromCart(item) == /\ cart = "active" /\ item \in items /\ items' = items \ {item} /\ cart' = IF items' = {} THEN "empty" ELSE "active" /\ UNCHANGED <<order, shipment, status>> /\ history' = Append(history, <<"RemoveFromCart", item>>) \* Abandon cart (timeout or explicit) AbandonCart == /\ cart = "active" /\ cart' = "abandoned" /\ status' = "browsing" /\ UNCHANGED <<order, shipment, items>> /\ history' = Append(history, <<"AbandonCart">>)
Order Actions
\* Convert cart to order (checkout) CreateOrder == /\ cart = "active" /\ items # {} /\ order = "pending" \/ order = "none" /\ cart' = "converted" /\ order' = "pending" /\ status' = "checkout" /\ UNCHANGED <<shipment, items>> /\ history' = Append(history, <<"CreateOrder", items>>) \* Confirm order (inventory reserved) ConfirmOrder == /\ order = "pending" /\ order' = "confirmed" /\ UNCHANGED <<cart, shipment, status, items>> /\ history' = Append(history, <<"ConfirmOrder">>) \* Payment successful PayOrder == /\ order = "confirmed" /\ order' = "paid" /\ status' = "processing" /\ UNCHANGED <<cart, shipment, items>> /\ history' = Append(history, <<"PayOrder">>) \* Cancel order CancelOrder == /\ order \in {"pending", "confirmed"} /\ order' = "cancelled" /\ status' = "failed" /\ UNCHANGED <<cart, shipment, items>> /\ history' = Append(history, <<"CancelOrder">>)
Shipment Actions
\* Create shipment after payment CreateShipment == /\ order = "paid" /\ shipment = "none" /\ shipment' = "preparing" /\ UNCHANGED <<cart, order, status, items>> /\ history' = Append(history, <<"CreateShipment">>) \* Ship the order ShipOrder == /\ shipment = "preparing" /\ shipment' = "shipped" /\ status' = "shipped" /\ UNCHANGED <<cart, order, items>> /\ history' = Append(history, <<"ShipOrder">>) \* In transit InTransit == /\ shipment = "shipped" /\ shipment' = "in_transit" /\ UNCHANGED <<cart, order, status, items>> /\ history' = Append(history, <<"InTransit">>) \* Delivered Deliver == /\ shipment = "in_transit" /\ shipment' = "delivered" /\ status' = "completed" /\ UNCHANGED <<cart, order, items>> /\ history' = Append(history, <<"Deliver">>) \* Return initiated ReturnOrder == /\ shipment = "delivered" /\ shipment' = "returned" /\ order' = "refunded" /\ UNCHANGED <<cart, status, items>> /\ history' = Append(history, <<"ReturnOrder">>)
Invariants: Detecting State Mismatches
Consistency Invariants
\* Cart and Order consistency CartOrderConsistency == \* If order exists, cart must be converted or active (order # "none" /\ order # "pending") => cart \in {"converted", "active"} \* Order and Shipment consistency OrderShipmentConsistency == \* Shipment only exists for paid orders (shipment # "none") => order \in {"paid", "refunded"} \* Shipment and Status consistency ShipmentStatusConsistency == \* If delivered, status must be completed (shipment = "delivered") => status = "completed" \* No orphaned shipments NoOrphanedShipments == \* Cannot have shipment without a paid order (shipment \in {"preparing", "shipped", "in_transit", "delivered"}) => order \in {"paid", "refunded"} \* Items consistency ItemsConsistency == \* If cart is empty, no items should exist (cart = "empty") => items = {}
Temporal Properties (Safety)
\* Safety: Order never goes backwards OrderNeverGoesBackwards == [](order = "paid" => [](order # "pending")) \* Safety: Delivered items stay delivered (unless returned) DeliveredStaysDelivered == [](shipment = "delivered" => [](shipment \in {"delivered", "returned"})) \* Safety: Cancelled orders never ship CancelledNeverShips == [](order = "cancelled" => [](shipment = "none"))
Temporal Properties (Liveness)
\* Liveness: Paid orders eventually ship PaidOrdersEventuallyShip == (order = "paid") ~> (shipment = "shipped") \* Liveness: Shipped orders eventually complete ShippedEventuallyCompletes == (shipment = "shipped") ~> (status = "completed")
Failure Scenarios
Network Partition Examples
\* Scenario: Order service confirms, but shipment service doesn't receive message PartitionAfterOrderConfirm == /\ order = "paid" /\ shipment = "none" \* Stuck - shipment never created \* This violates: PaidOrdersEventuallyShip \* Scenario: Shipment delivered, but status service unreachable PartitionAfterDelivery == /\ shipment = "delivered" /\ status = "processing" \* Stale status \* This violates: ShipmentStatusConsistency
Race Conditions
\* Scenario: Customer cancels while shipment is being created RaceCancelAndShip == \* Two concurrent actions: \* 1. Customer clicks "Cancel Order" \* 2. Warehouse starts preparing shipment \* \* Possible outcomes: \* - Order cancelled, shipment cancelled (correct) \* - Order cancelled, shipment proceeds (BAD: shipped cancelled order) \* - Shipment created, cancel fails (acceptable: customer can return) \* The invariant that catches this: /\ order = "cancelled" /\ shipment # "none" \* This violates: CancelledNeverShips
Model Configuration
\* Initial state Init == /\ cart = "empty" /\ order = "none" /\ shipment = "none" /\ status = "browsing" /\ items = {} /\ history = <<>> \* All possible transitions Next == \/ \E item \in Items: AddToCart(item) \/ \E item \in items: RemoveFromCart(item) \/ AbandonCart \/ CreateOrder \/ ConfirmOrder \/ PayOrder \/ CancelOrder \/ CreateShipment \/ ShipOrder \/ InTransit \/ Deliver \/ ReturnOrder \* Specification Spec == Init /\ [][Next]_vars /\ WF_vars(Next) \* Properties to check THEOREM Spec => []CartOrderConsistency THEOREM Spec => []OrderShipmentConsistency THEOREM Spec => []ShipmentStatusConsistency THEOREM Spec => []NoOrphanedShipments ====
Running the Model Checker
TLC Configuration
Create a ECommerceStates.cfg file:
CONSTANTS
Items = {i1, i2, i3}
MaxQuantity = 5
INIT Init
NEXT Next
INVARIANTS
CartOrderConsistency
OrderShipmentConsistency
ShipmentStatusConsistency
NoOrphanedShipments
ItemsConsistency
PROPERTIES
PaidOrdersEventuallyShip
ShippedEventuallyCompletes
Expected TLC Output
Model checking completed. No error has been found. Estimates of the progress of the simulation: Coverage: 847 states generated, 234 distinct states found Depth: 12 states deep Checking temporal properties... Property PaidOrdersEventuallyShip is satisfied. Property ShippedEventuallyCompletes is satisfied.
Lessons for System Design
Key Insights
- State machines are implicit - Most e-commerce systems have these states, but they're scattered across services without formal specification.
- Invariants catch bugs early - The consistency checks would have caught issues before production.
- Temporal properties matter - Liveness properties ensure the system makes progress.
Recommendations
| Issue | TLA+ Detection | Production Symptom |
|---|---|---|
| Orphaned shipment | NoOrphanedShipments |
Customer charged, nothing ships |
| Stale status | ShipmentStatusConsistency |
Customer sees "processing" for delivered item |
| Cancel race | CancelledNeverShips |
Cancelled order still ships |
| Stuck order | PaidOrdersEventuallyShip |
Paid order never ships |
Integration with OpenAPI/PetStore
The PetStore API defines similar states:
Order:
status:
enum: [placed, approved, delivered]
Pet:
status:
enum: [available, pending, sold]
This TLA+ spec extends those simple enums into a complete state machine with transition guards and consistency invariants.
References
- TLA+ for System Design: Main Guide
- OpenAPI PetStore Example
- TLA+ Home Page
- Lamport, L. "Specifying Systems" (2002)