# Food Ordering & Delivery Tracking — decision log

The Phase 5 answer key. Every entry is a question an interviewer actually asks ("why is that a
separate class?") and the answer that earns the mark: **what change the seam anticipates, and what
the change would cost without it.**

Fifteen classes, none over ~120 lines and most under 40. `OrderBook` holds the *coordination* of an
order and not one rule that a requirement could reach: no `switch`, no `if (state == PREPARING)`, no
clock reading, no notification and no arithmetic.

| File | One reason to change |
|---|---|
| `OrderBook` | the *coordination* of an order changes — i.e. a new operation appears |
| `OrderBookFactory` | what a standard platform is made of changes |
| `OrderStage` | the five steps every stage runs change — i.e. never again |
| `PlainStage` | nothing; it exists so a stage with no behaviour can be a value |
| `PlacedStage` | *when* the price is fixed changes |
| `DeliveredStage` | the late-delivery promise changes |
| `CancelledStage` | who may cancel, and when, changes |
| `Lifecycle` | how stages are indexed changes — i.e. never again |
| `Stages` | the lifecycle gains, loses or replaces a stage |
| `Order` | the facts an order holds change |
| `Tariff` / `StandardTariff` | what we put on the bill changes |
| `DeliveryFeePolicy` / `DistanceBandFee` | what it costs to carry a bag changes |
| `ConsoleNotifier` | how we show notifications happening changes |
| `Demo` | what we show when we run it changes |

---

## Seam 1 · `OrderStage` — the template method, and the point of the problem

**Anticipates:** every new stage, and every rule about when a stage is allowed or what it does for
itself. A confirmation step, a collection-from-the-counter step, a scheduled start, a stage for
orders that went wrong, a stage that applies a refund.

**The one decision to defend, if there is time for only one.**

The requirement said the same five things happen every time an order moves: validate the transition,
apply it, timestamp it, run that stage's own work, notify that stage's audiences. And it said only
the last two vary. `OrderStage` **is** that sentence:

```java
    public final StageEvent enter(Order order, Clock clock, Notifier notifier) {
        Instant at = clock.instant();       // 3 · the timestamp, read once and up front
        check(order, at);                   // 1 · validate the transition
        order.moveTo(state);                // 2 · apply it
        onEntered(order, at);               // 4 · this stage's own work
        StageEvent event = new StageEvent(state, at, tell(order, notifier));   // 5 · notify
        order.record(event);
        return event;
    }
```

`enter` is `final`. That word is the seam. A stage can fill in the two varying steps and **cannot**
reorder, skip or wrap the fixed ones: it cannot arrange to be timestamped after it is applied, cannot
notify twice, cannot notify before its own work has run, and cannot forget to record itself — because
it does not call any of those things.

**What that buys, concretely.** "Every stage is timestamped", "the clock is read exactly once per
transition", "the customer is always told first", "a stage is recorded before anybody is told" and
"a refused transition changes nothing" are true **of stages that do not exist yet**, and true without
a single stage subclass containing a line about clocks, notification order or history.

**Why a template method and not a policy interface.** This is the first thing an interviewer probes,
because it is the difference between two syllabus items. Both shapes exist in this design on purpose,
so they can be compared:

|  | policy (`Tariff`, `DeliveryFeePolicy`) | template method (`OrderStage`) |
|---|---|---|
| who controls the sequence | the caller | the skeleton |
| what the implementation knows | one question | one step |
| what swapping it changes | an **answer** | a **step** |
| can it get the order of things wrong | it never sees the order of things | it cannot see the order of things |

Had the five steps been an interface each stage implemented, every stage would have to remember to do
all five, and the fifth stage anybody added would forget one. That is not a hypothetical: three of
the six stages in this lifecycle override *neither* hook, and each of those three is a single
constructor call via `PlainStage` — a name and a list of people to tell. **Half the lifecycle is
data.** Without the skeleton each of those three would be a class or a branch that had to remember to
read the clock, record an event and notify in the right order: three chances to get the same thing
wrong three different ways.

**Why the audiences are a field and not a third hook.** Who to tell is *data about* a stage, not
behaviour of one. As a field it can be read, printed, diffed and asserted, and a new stage supplies
it in a constructor call rather than in a method body. An overridable `audiences()` returning a
constant would invite somebody to compute it, and "who gets told" would stop being inspectable.

**Why the clock is read before the guard, not at step 3.** The timestamp *is* step 3, but the
*reading* is taken once at the top and handed to both hooks. A guard about time — "not before the
booked hour" — and the instant that ends up in the history have to be the same instant, or an order
could be refused for being twenty milliseconds early and then, on the retry, be recorded as having
started before its booked time. One read makes that unrepresentable. Curveball 1 is exactly this
case, and it needed no change here.

**Cost without it.** Curveball 2 asks for a new way for an order to stop. Written out inside the new
method — the obvious "it's just one method" version — it is **25 measured added lines** in the class
that coordinates every order on the platform, and it is the fifth copy of the five steps. With the
skeleton it is a new file plus **7 lines**, five of which are `cancel` with a different stage named.

---

## Seam 2 · `Lifecycle` + `Stages` — the sequence is a list, deliberately *not* a transition table

**Anticipates:** a stage added, removed, reordered or replaced. Curveballs 1 and 2 are each one line
here.

**Why not a transition table.** Have this ready, because a `from × event → to` table is the other
right answer to a problem shaped like this one and an interviewer will ask why it was not used.

A table earns its keep when the interesting question is *where can I go from here* — many states,
several events, a sparse grid, and a missing cell that **is** the refusal. Here `advance` takes no
event, because from any stage there is exactly one place to go. A table over that degenerates into a
single column: a successor list, written down a second time next to the stages that already imply it.
Two places that know the order of the lifecycle is one more than the requirement has.

What genuinely varies in this problem is not *where* a transition may go but **what happens when it
arrives** — the timestamp, the work, the audience, the money. So the design puts its weight on the
per-stage skeleton and keeps the ordering as the cheapest possible thing: a list. Add a state whose
legality depends on an event and the balance tips back, and that is the trade to name out loud rather
than to hide.

**Why `Lifecycle` and `Stages` are two files.** They change for different reasons. `Lifecycle` is an
indexing mechanism — a list plus an `EnumMap` — and it is finished; it knows nothing about what any
stage does. `Stages` is the requirement's table transcribed, and it changes every time the platform
learns a new way for an order to behave. Splitting them is what makes a new stage a *value* rather
than an edit to the class that walks the chain.

**Why cancellation is in a by-name map rather than a special case.** `OrderBook.cancel()` has no
conditional in it: it asks the lifecycle for the cancelled stage and asks that stage to take the
order. Cancellation is not a branch in the coordinator; it is a stage you reach by asking for it. The
proof that this was the right shape is curveball 2 — the restaurant's refusal is a second entry in
the same map, and the two methods that reach them differ by one identifier.

---

## Seam 3 · `Tariff` and `DeliveryFeePolicy` — two policies, one level apart

**Anticipates:** `DeliveryFeePolicy` — new distance bands, a flat fee, free delivery over a distance,
surge pricing in bad weather, a per-restaurant fee. `Tariff` — a promotion, a loyalty rate, a service
charge, a tax, a voucher.

**Why two and not one.** They are different kinds of decision on different clocks.
`DeliveryFeePolicy` is **logistics**: what it costs to carry a bag that far. `Tariff` is
**commercial**: what we choose to put on the bill. A loyalty scheme moves the second and has no
opinion whatsoever about the first, and a new distance band moves the first and no opinion about the
second. Collapsing them means marketing's percentages live in the class that knows what a kilometre
costs.

**Why `Tariff` returns the whole `OrderTotal` and not one `long`.** So a commercial decision can move
what is charged while leaving the quote alone. A method returning a single number has nowhere to say
*"the delivery still cost 2750; we are only asking for 2000 of it."*

**Why the fee's ceiling is `(metres + 999) / 1000` and not `Math.ceil`.** A double has no business
near money. `Math.ceil(2999 / 1000.0)` is right today and is one refactor away from `2.9999999` and a
fee that is 250 short.

**Cost without it.** Curveball 3 asks for a member discount. As a clause inside `StandardTariff` it is
**8 measured lines**, and it makes that class's own javadoc — *"nothing is knocked off here"* —
untrue, so the fourth tier is a third `else if` and the weekend promotion is an edit to a literal
inside the class that prices every order. As a decorator it is a new file and **2 lines** of wiring.

---

## Seam 4 · `OrderBookFactory` — construction is a job, and it has an owner

**Anticipates:** every change to what a standard platform *is*. A new fee band, a promotion, a
loyalty scheme, a new stage, a stage that can be scheduled, a different notification channel.

Construction changes for entirely different reasons from operation. `OrderBook` changes when the
*coordination* of an order changes, which is almost never. This changes whenever the business changes
its mind. All three requirement changes land in the wiring — this class or the stage list beside it —
and each changes exactly one line of it.

`standardTariff()` is exposed on its own so a variation on the standard platform reuses what it is
not changing instead of copying it. That is what makes curveball 3 a wrapper around a call rather
than a second copy of the tariff.

**`OrderBook` has no default constructor, and that is deliberate.** The moment it can supply its own
clock, it can read the wall clock, and the 45-minute rule stops being testable. The same argument
applies to the notifier: a class that can invent its own notification channel cannot be handed one
that fails.

---

## Side effects · what happens when a notification throws

This is the behaviour most likely to be guessed wrongly and it is the part of this problem that is
*not* a state machine, so it is worth being precise about.

- A throw **does not roll the transition back.** The order really has reached the stage, its
  timestamp stands, its own work has already run, and `state()` reports the new stage. The order
  moved; somebody just was not told.
- A throw **does not stop the remaining audiences.** Each is attempted independently, in order.
- `StageEvent.notified()` lists only the audiences told **successfully**. If the restaurant's tablet
  is down, a `PLACED` event reads `[CUSTOMER]`. The loss stays visible instead of being invented.
- Nothing is retried and nothing is queued. Retrying is a different problem and it belongs to
  whoever owns the channel, not to a stage.

**Why none of that needed a try/catch around a rollback path.** Because of *where* notifying sits in
the skeleton. It is step 5: the order has been applied, worked and recorded before anybody is told,
so there is nothing half-done for a throw to leave behind. "A failed side effect does not roll the
transition back" is free — a consequence of the ordering rather than a code path that has to be kept
correct. The mirror image is the refusal guard: it is step 1, before anything has changed, so "a
refused transition changes nothing, notifies nobody and leaves no history entry" is also free.

**Where a `catch` does appear**, it is inside `tell`, per audience, and it swallows on purpose with a
comment saying so. A swallowed exception with no comment is a bug; a swallowed exception with the
requirement quoted next to it is a decision.

---

## Errors · plain JDK exceptions, and which one wins

Two exception types, both from the JDK, and a stated precedence:

1. `IllegalArgumentException` — no order has that id. Checked **first**, on every method that takes
   an id.
2. `IllegalStateException` — the order exists, but this transition is not legal from where it is.

**Why not a domain exception hierarchy?** Because there is nothing for a caller to *do* differently.
Every refusal here means the same thing to the caller — you asked for something that is not allowed
right now, and nothing happened. A hierarchy earns its keep when the caller branches on the leaf, as
it does in a vending machine where "sold out" and "insufficient funds" lead to different customer
journeys. Adding five exception classes nobody catches separately is ceremony, and it is worth being
able to say which of the two situations you are in rather than reaching for the hierarchy every time.

**Why the id check wins.** `find` is the first line of every method, so an unknown id can never be
reported as a state problem. Getting this the other way round produces the confusing failure where
`cancel("typo")` complains about the order's stage.

---

## Money · three numbers, and why the third is stored

`OrderTotal` is `itemsMinor`, `deliveryFeeMinor`, `totalMinor`. The first two are **quotes** — facts
about the order, printed on the receipt, frozen at placement. The third is a **decision**, and
decisions move.

Today `totalMinor` usually equals the other two added up. It is a separate field because "what we
quoted" and "what we charge" are two different things, and the day they differ you want somewhere to
put the difference. `adjustmentMinor()` then reports the difference without anybody storing it.

**Why `Order` has `quote()` and `charge()` rather than a setter.** The mutators are named for the
domain, and they are as narrow as they can be: `quote` can be used once — a quote that could be
replaced is not a quote — and `charge` can only move what is charged. "The quote never moves after
placement" is enforced by there being **no method that could move it**, which is a much stronger
statement than a comment asking people not to.

**Why waiving subtracts instead of overwriting.** `DeliveredStage` does
`charge(total - deliveryFee)`, not `charge(items)`. The two expressions are equal today, which is
exactly the kind of coincidence worth not relying on — and curveball 3 is the day it stops being
true. A member's discount plus a late delivery has to charge `items - discount`; the overwriting
version would silently have thrown the discount away, with nothing failing to compile and no old test
going red. **An adjustment must say what it takes off, not what the answer is.** Two adjustments that
each assert the final figure cannot compose, and which one wins depends on the order the stages
happened to run in.

Every amount is minor units in a `long`. No doubles, ever, so the only rounding in the problem is the
distance ceiling and it is integer arithmetic.

---

## Identity and values

`Order` is an **entity**: identified by its `id` and by nothing else. Two orders for the same food
from the same customer in the same second are two orders, so equality is never about contents.
`equals` is left as identity on purpose, and nothing ever looks an order up by anything but its id.

Everything crossing the API boundary is a **record** with its invariants in the compact constructor,
so an invalid instance cannot exist to be passed around: a line for zero items, a negative price, an
order with no lines, a blank customer id and a negative total are all rejected at construction rather
than checked later by whoever remembers to.

`Order` is the only mutable thing in the design, and it knows none of the rules — not which stage may
follow which, not who gets told, not when cancellation closes, not what anything costs. It holds four
facts and offers the narrowest mutator for each. **That is why a new stage never edits it.**

---

## Collections never escape

`history()` returns `List.copyOf`, so a caller can read the whole life of an order and cannot add a
stage to it — and a history handed out earlier does not grow underneath its holder. `OrderRequest`
copies its line list on construction, so a caller cannot change an order by holding the list it was
built from. `StageEvent` copies its audience list. `OrderStage` copies its audiences once, at
construction, and exposes them as an immutable list.

---

## Time is injected, and there is only one notion of "now"

Stages are timestamped, so time is a dependency and it is supplied. Nothing in the design calls
`Instant.now()`. `Entry.create()` hands over a clock *fixed* at `2026-08-18T12:00:00Z`, so every
stage of an order carries the same instant and no delivery is ever late — which is exactly what the
tests that are not about time want. `Entry.create(Clock, Notifier)` is the fully injected form.

`DeliveredStage` compares the instant it was handed with the instant in the order's first history
entry. Both come from the injected clock, so the late-delivery rule is decided entirely from recorded
facts, and a stage can never disagree with its own timestamp.

---

## Concurrency is absent, deliberately

The requirement says one call at a time, so there is not a lock, a `synchronized` or a concurrent
collection anywhere. `LinkedHashMap` is chosen for insertion order, not for speed. Say this out loud
rather than adding locks speculatively: an unnecessary lock is a claim about contention that has not
been measured, and it hides the fact that `Order` is the shared mutable state that would need
protecting if the requirement ever changed.

---

## What was deliberately *not* built

The menu and any price lookup; payment, refunds beyond charging nothing, and restaurant payouts;
persistence; restaurant capacity, opening hours and kitchen queues; couriers as entities, assignment
and routing; ratings and support tickets; retrying or queueing a failed notification; and anything
about threads. Each of those is a different problem, and saying so is cheaper than half-building it.

---

## The three requirement changes, measured

| Curveball | What it asks | Where it landed | Measured |
|---|---|---|---|
| 01 · scheduled orders | an order booked for later must not be cooked before its time | a new stage overriding `check`, plus one line of `Stages` | **2 lines** (budget 3) |
| 02 · the kitchen says no | the restaurant can refuse, and that is not a cancellation | a new stage overriding both hooks, one line of `Stages`, five lines of delegation in `OrderBook` | **7 lines** (budget 11) |
| 03 · loyalty rate | members pay less for the food, and the late promise applies on top | a decorator over `Tariff`, plus one line of `OrderBookFactory` | **2 lines** (budget 3) |

`OrderStage` — the skeleton every transition runs — was not opened by any of the three. Neither were
`Order`, `Lifecycle`, `PlacedStage`, `DeliveredStage`, `CancelledStage`, `PlainStage`,
`StandardTariff`, `DeliveryFeePolicy` or `DistanceBandFee`. The base suite of 23 tests stayed green
under all three, which is the claim that matters most: each requirement said the orders that came
before it must not notice.

The one file two of the three did touch is `Stages` — a list of values. That is the intended
destination, and it is the whole argument for keeping the sequence separate from the skeleton.
