The method at operating altitude. Part A is the sheet you fill in, one bite at a time; Part B is the reference you consult while filling it. Foundations and the four spiral passes are the full treatment; nothing here is new, and everything here is enough.
One rule governs the whole sheet: nothing about the business without the business. The questions below are not a design puzzle to solve at your desk. Most of them are interview questions, and the register is the transcript.
You never decompose the whole vision at once, because you never have to. Take one operation — book a seat — and complete its six properties. Then take the next.
| Property | Your answer |
|---|---|
| Trigger — what makes it run: an external request, a published event, or another use case invoking it. Not the transport. | |
| Typed input — the least the trigger can carry, as a precise type | |
| Typed output — what it produces on success | |
| Typed failures — the enumerable, closed set of ways it can fail, each a named domain fact | |
| Steps — the internal sequence, named in domain terms | |
| Dependencies — what must precede what, what can run in parallel, what is conditional |
Filling this in is already verification, not only specification. A flaw shows up as a property you cannot complete — an outcome with no failure named for it, a step needing a fact no earlier step produces — and it shows up now, on one page, before a line is written.
Before going further, decide what you are holding. The discriminator is not the number of steps; it is whether a step could stand alone under its own trigger, and whether state has to outlive a single invocation.
Ask the business the one question this altitude forces: who, or what, would ask for this to change? Each independent decision authority is a driver — a stakeholder or team, a regulator, an external partner on its own release schedule, a pricing policy, a technical concern such as storage.
Write one line in the change-driver register:
| Use case | Driver | Confidence | Source |
|---|---|---|---|
| measured / elicited / low-confidence prior | git history / interview / borrowed |
Never re-interrogate a driver already named. Once it is in the register, the next use case answering to it is filed under it. That economy is the whole discipline.
Mark the confidence honestly. Where the volatility is already realized, the driver is a fact you can measure and version-control history confirms or corrects it. Where it is not, the same question returns a prediction, and businesses are routinely wrong about their own future. Record it as a low-confidence prior, marked as such, and let co-change history promote it later.
Do not model entities. For each step that learns something durable, record what it fastens to which identity:
| Field | Identity it attaches to | Owner (the operation that creates it) | Readers |
|---|
Two gates apply to every row:
For every step that can be invalidated after earlier steps have changed state, name one of three. Check design-out first.
| Invalidated step | Response | Why |
|---|---|---|
| design-out / BER / FER |
Mixed strategies are normal: BER for money, FER for telemetry, design-out for collaborative state, coherently, at once.
You do not design a hierarchy. Watch the register: the use cases sharing a driver are already a group. Give the group a name and it is a workflow. The workflows sharing a concern are a cluster; name it and it is a subsystem.
Before accepting any grouping, run both directions of the test:
The sharpened test asks both at once: does this one change force all of these, and only these, to change?
Pass both and the group is cohesive. Pass one and it is not.
The register is the trail, and it does triple duty: it is the grouping itself, it is the completeness-and-purity checklist, and it is what version-control history can confirm. Keep it live.
Two entries deserve a note beside them:
T — the value exists, unconditionally. No absence, no failure, no waiting.Option<T> — it may or may not exist, and its absence is a domain fact, not an error.Result<T> — the operation may or may not have produced it; failure is a typed outcome, synchronous.Promise<T> — it arrives later and may fail.Asynchrony emerges from leaves: any operation touching I/O is a Promise, and it propagates outward. It is not decided at the top.
Parse, don’t validate. A type carries a claim and construction enforces it, so a process body needs no defensive checks. Enforcement has levels — type-level, construction-level, runtime-level, convention-level — and the commitment is identical across all of them; only the teeth differ.
Each function implements exactly one pattern; mixing patterns is the signal to split.
How steps reach one another is a second question with two answers — direct step composition (a step calls the next, composing on the returned value) and event-based step composition (a step publishes a typed fact and the next is triggered by it). Not a timing distinction and not an altitude distinction; the methodology names both and prefers neither.
| Altitude | What it is | Formed by |
|---|---|---|
| Use case | one business operation, one trigger, one outcome | nothing — it is the floor |
| Workflow | a composition of use cases for one business outcome | a business policy |
| Subsystem | a coherent business concern, a cluster of workflows | a domain concern |
| System | the composition of subsystems | the product boundary + operational envelope |
A unit’s composition at one altitude is a Leaf at the altitude above. The patterns recur because the structure is fractal. The book stops at the system on purpose: above it the composing force is organizational, not change-driver cohesion the code can express.
Keep two operations distinct: within-altitude composition is the patterns, and happens at every altitude including the use case. Cross-altitude grouping is change-driver cohesion, and happens only where a level forms from the one below.
Two ways, and they check each other.
Two cautions, mirror images of each other:
Why attribution rather than comparison. Deciding cohesion pairwise is quadratic in the number of use cases and re-run from scratch every time one is added. Attribute each use case to its driver once and cohesion stops being a comparison and becomes a sort — one pass to attach the labels, then the use cases sharing a driver fall out as the groups. That is quasi-linear cohesion, and it is why the register scales.
fresh → stale → expired.Which applies is a judgment across four axes — reversibility, forward-progress value, domain shape, coordination cost.
Where the invalidation is a race, one principle with a family of tactics: move the contention to a single named coordination point, and make the conflicting state impossible to write rather than something detected after it is written.
Locking is the admission that the conflict was left constructible; design-out is the decision that it never was.