B8.1 · dive 2 — the Decider
What a command means: events out, never a mutation.
The decision half of the engine answers the second question: what a command means when it
meets a book. The functional event-sourcing Decider, as Chassaing fixed it, is three types and three
elements — initialState, decide : Command -> State -> Event list, and
evolve : State -> Event -> State. decide is the only place business rules live:
it returns the facts that follow, never a mutation. evolve folds a fact into state, and it is
deliberately trivial.
Exchange.Decider and Exchange.OrderBook are PROPOSED
pure modules; Exchange.Book is the PROPOSED GenServer shell (docs/trading/).
They are taught as design; none has a rung yet, and none claims a number.
ORD-a1). Feed a command; decide computes the events (pure), then
evolve folds each into the new state. No process, no store — the fold law,
visible.ORD-a1: 5 @ 100. Feed a command — decide returns the
events, evolve folds them to the new state.
Every line is computed by the two pure functions over the fixed book.The right seat for a matching engine
docs/trading/trading.patterns.md · Part oneThe pattern is three types and three elements, quoted from the corpus:
initialState : State
decide : Command -> State -> Event list
evolve : State -> Event -> State
decide is the only place business rules live: given a command and the current
state, it returns the facts that follow — possibly several, possibly none, never a mutation.
evolve folds a fact into state, and it is deliberately trivial — "Given current State, when
Event occurs, here is the new State" — a field set, a list append, a counter move; decision has already been
taken. The discipline the two signatures enforce is the whole value: rules cannot hide in mutation, because
there is no mutation; state cannot drift from the log, because state is the fold of the log.
Three properties a matching engine cannot negotiate away, each a direct corollary of the
signatures. Testability without machinery — price-time priority, self-trade prevention, partial-fill
arithmetic are properties over decide alone: feed a book state and a command, assert on the
returned events; no process, no store, no mock. Replay as identity, not feature — fold
evolve over the log and the book is its history; the Chapter 4.4 journal already gates
exactly this shape (replay reproduces live state), so milestone A inherits a certified mechanic rather than
promising one. Audit as a property — every fill is explainable as
decide(cmd, state_at_seq), and because sequence is the branded mint order, "state at sequence"
is a well-defined point, not a race.
Event sourcing, not command sourcing
docs/trading/trading.patterns.md · the distinctionOne distinction worth pinning because the literature blurs it: this is event
sourcing, not command sourcing. The log stores decide's outputs — the facts — not its
inputs. Storing commands and re-deciding on replay couples history to every past version of the rules;
storing events makes replay a pure fold under evolve, which rarely changes and changes safely.
The platform logs facts.
The replay corollary is the one place the design leans on the as-built floor: the Chapter 4.4 journal already gates "replay reproduces live state," so the book's replay rung re-gates a posture this course committed, rather than inventing one. The journal's record is committed today; the book's own replay rung is not yet run, and so the design claims no number for it.
The three corollaries follow from the one choice: testability is a property over
decide; replay is the fold the B4.4 journal already gates; audit is decide
re-applied at a sequence the id law makes exact.
Alternatives weighed
docs/trading/trading.patterns.md · the honest comparisonThe pattern earns its seat against the alternatives, named honestly. Select a row to read how each fares against the three properties and the seam it costs.
| Approach | Testable rules | Replay free | Audit a property |
|---|---|---|---|
| the Decider | yes | yes | yes |
| the mutating aggregate | drags machinery | built-for, not free | via the entity |
| transaction script / CRUD | over rows | no fold | forensic only |
| the saga as decision seat | worse tests | one level up | across aggregates |
| actor state, no events | over a process | surrendered | surrendered |
Functional core, imperative shell
docs/trading/trading.patterns.md · the BEAM placementThe functional core, imperative shell, as the house already practices it:
Exchange.Decider and Exchange.OrderBook are pure modules; Exchange.Book
is the GenServer shell that owns the process identity, drains the ingress Ring, calls decide,
appends the events to the log, folds evolve, and answers. The shell is thin enough to be boring,
which is the point — everything worth testing is underneath it, and everything worth supervising is the
shell.
All three Exchange.* modules are PROPOSED — the trading engine on top of
a real substrate. The shell composes EchoCache.Ring
(dive 1) and, at the next module,
EchoCache.Journal — both shipped Elixir in the live umbrella, hardened by the rung-gated EchoMQ
program (/echomq, docs/echo_mq/emq.roadmap.md), which names the trading
platform as its downstream consumer. The pure cores are the engine's to build, and they earn their first
numbers at their own rungs; the substrate beneath them already has its numbers. Where the Decider is the
wrong tool, named: cross-aggregate invariants — "this account's margin across all books" — do not belong
inside one book's decide; they are the saga's job, downstream of the log, with compensating
events. Long or effectful work inside decide is a category error: decide is pure by
signature, and IO belongs at the shell. The order book whose priority falls out of the id law is
B8.1.3.
References
sources · relatedSources
- Chassaing — Functional Event Sourcing Deciderthe decide / evolve signatures, initial state, and the command-sourcing distinction this dive teaches
- Fowler — The LMAX Architecturein-memory event sourcing on a single writer — the architecture the Decider sits inside
- The LMAX Disruptor (technical paper)the ring that feeds the single-writer processor the Decider runs in
Related
- B8.1 — The Enginethe module hub; the engine's two questions
- B8.1.1 — The Disruptor Seatthe ingress the Book's shell drains
- B2 — The Elixir BCS Corethe functional core the Decider is written in
- B4 — EchoCachethe journal the replay corollary leans on
- Functional Programming in Elixirthe umbrella the runtimes live in
- EchoMQ — the coursethe bus protocol in rung-level depth