Food Delivery System
Three actors need to agree on the state of one order at once: the restaurant cooking it, the delivery partner carrying it, and the customer waiting for it. The design problem is keeping that agreement consistent without any of the three polling the other two.
Requirements
Functional
- A
Restaurantpublishes a menu ofMenuItems; aCustomerplaces anOrderagainst one restaurant's menu. - Once placed, an order moves through a fixed lifecycle: placed, accepted by the restaurant, being prepared, ready for pickup, out for delivery, delivered.
- When an order is ready for pickup, the system assigns it to a
DeliveryPartnerusing a matching strategy, not a fixed one-partner-per-restaurant rule. - Only valid forward transitions are allowed - an order can't jump from placed straight to delivered, and it can't move backward.
Non-functional
- Assigning a partner should be swappable (nearest-available today, load-balanced or rated-based tomorrow) without touching the order lifecycle code.
- An order's current status must be a single source of truth that every actor reads the same way, not three separate flags that can disagree.
Design
Order owns a status field that only moves forward through a fixed OrderStatus
sequence, enforced by one transitionTo method - nothing sets status directly. Partner
assignment is pulled out into a DeliveryAssignmentStrategy, handed the pool of available
partners and the restaurant's location, exactly the same shape as the fee and pricing
strategies elsewhere in this module.
- 1The order starts in PLACED, before the restaurant has even seen it.
- 2Every status change goes through the same guarded method.
- 3Only once it's ready does assignment even begin.
- 4The order delegates "who delivers this" entirely to the strategy.
- 5The partner updates its own availability - the order never touches that field directly.
Order never reaches into DeliveryPartner to change the partner's own status - instead
each side updates only what it owns (Order.status, DeliveryPartner.available), which
means a partner going offline mid-delivery is a partner-side concern, not something that
corrupts the order's lifecycle.
Class diagram
Code
Design decisions
- Status transitions are validated in one method,
transitionTo, against an explicit allowed-next-states map. Scatteringorder.status = Xassignments across the codebase means nothing stops a bug from settingDELIVEREDon a just-placed order. Centralizing the check means every illegal jump fails the same way, in the same place, the first time it's attempted. - Partner assignment is a
DeliveryAssignmentStrategy, evaluated only once the order hitsREADY_FOR_PICKUP. Assigning too early (right when the order is placed) would lock in a partner before anyone knows how long preparation takes, wasting their time waiting; tying assignment to the status transition itself keeps the two concerns in sync automatically rather than by convention. DeliveryPartner.availableis a field the partner's own methods flip, not somethingOrdersets directly.Ordercallspartner.markBusy()rather thanpartner.available = false- the partner is the one thing that should decide what "busy" means for itself, especially once real systems add partners juggling more than one active delivery.- What's missing for a real system: the matching strategy here only looks at current
availability and distance; a production system also needs partner capacity limits (some
can carry two orders at once) and a reassignment path for when an assigned partner cancels
mid-flight, neither of which changes the shape of
Order's status machine, just what feeds into it.