F5 · the engine · module 5
Commands, queries & events
The enroll command is contract-checked, but the engine still mixes two jobs — changing things
and answering questions — and keeps no record of what changed. F5.05 separates them.
A command changes state and returns only whether it worked; a query returns
data and changes nothing; and every change is recorded as a past-tense event —
%LearnerEnrolled{}, %LessonDelivered{}. Once changes are
events, state stops being something you store and becomes something you derive: fold the events
and you have the current state. That makes the engine a reducer — two pure functions, run by a fold.
Two paths through the engine and one record of change. Select a piece to see what it does and what it returns.
State is a left fold over the log
reduce(events, %{}, &evolve/2)
State starts empty. Each event folds in to derive the state at that point.
Writes, reads, and events
Reading the diagram: a command runs, emits an event, and the event folds into state — that is the write path. A query reads the same state and returns data without touching it — the read path. The event in the middle is what ties them together and what makes replay possible. Select a piece.
Through the engine · select a piece
piece: Command
in the Portal: enroll(user_id, course_id)
Split writes from reads and record every change, and the engine gains a memory. State becomes a function of its history rather than a mutable pile you hope is correct.
Three deep dives
In order: the command/query rule and why it pays; modeling each change as a domain event; and folding those events so the engine becomes a reducer.
%LearnerEnrolled{} — immutable and carrying the data of what happened.reduce replays the engine to now.F5.04 · honest commands
Enroll checks its contract, but it both changes state and is the only record that anything happened.
F5.05 · split and record
Writes and reads part ways, every change becomes an event, and state is the fold of the log.
Start with command/query separation, then domain events, then the engine as a reducer. The next module, F5.06 — Where engine state lives, chooses the process that holds the folded state. For the runtime path, see the design brief: the command & event flow.
References
Separating commands from queries, and modeling change as events.
Sources
- Martin Fowler — CQRS — command/query responsibility separation.
- Martin Fowler — Domain Event — modeling change as events.
- Commanded — CQRS/ES building blocks in Elixir.
Related in this course
- F5.05.1 · Command/query separation
- F5.05.2 · Domain events
- F5.05.3 · The engine as a reducer
- F5.04 · Design by contract — the command this builds on.
- F5.0.3 · The command & event flow — the design brief for this path.