Chapter 6

The Body Plan

DNA is how a part works. Body plan is where it belongs and what it connects to. Draw the creature before you write the cell.

On this page

DNA makes the cell work. Body plan makes the creature coherent.


The frame

Software has DNA and a body plan.

DNA is the implementation inside each part: methods, data structures, conditionals, queries. Body plan is the organism-level design: what parts exist, where the joints are, what connects to what, and what each part refuses to know.

In plain terms: DNA is how a part works. Body plan is where the part belongs and what it talks to.

The two levels answer different questions.

The body plan answers:

  • What talks to what?
  • Who owns the decision?
  • What is replaceable?
  • Where is the boundary?
  • What does this object not know?

The DNA answers:

  • How is this method implemented?
  • What data structure fits here?
  • How do we name this?
  • How do we simplify this conditional?

Both matter. They are different work. Most small refactors and most of what an AI pair drafts live at the DNA level. The costly failures live at the other one.


The danger

Here is the trap: you can write excellent DNA for the wrong creature.

A class can be clean, tested, short, and idiomatic, and still belong to a bad body plan. "Good code" is not enough. Good code inside the wrong relationships still produces a system that is hard to change.

The failure mode has a name: the second nervous system. A parallel structure grows beside the one that already exists. Two ways to route the same kind of request. Two objects that own the same decision. One convention in most of the codebase and a second, cleaner one in the new corner. Each structure looks fine alone. Together they mean every future reader has to learn both, and every future change has to guess which one is load-bearing.

In review, that means the question is never just "is this class good?" It is "does this class belong to the creature we already have?"

Three ways it happens in practice:

The gamed spec. A unit spec built its object through a convenience constructor and asserted a literal string. A hand-rolled standalone class passed, green across the board, while silently breaking the construction contract the real caller depends on. Clean DNA. Parallel object. Wrong creature. Nothing in the test suite objected, because the test was certifying DNA instead of driving the body plan.

The tempting reuse. A new sales path needed fulfillment, and an existing payment orchestrator already did fulfillment. Reusing it would have dragged payment-provider assumptions into a sale that never touched that provider. The fix was a sibling orchestrator that drives the same downstream services and never calls the old one. Sometimes the second nervous system grows from reusing the wrong part, not from writing a new one.

The better convention. Design diagrams proposed idealized class names for a live app that already handled four commands in one consistent shape. Adopting the prettier names would have forked a second style. Match the repo, not the diagram. Two ways to do the same thing, one "better" and one existing, is worse than one consistent way.


The drawing test

The first design question is not "is this code clean?" It is: can I draw this system?

Boxes for objects. Arrows for messages. That is the whole test.

  • If you cannot draw it, the body plan is hidden.
  • If you can draw it but the arrows go everywhere, the body plan is tangled.
  • If one box has all the arrows, you found the god object.
  • If you can swap one box for another without redrawing the rest, the body plan has a good joint.

The drawing is not documentation. It is a diagnostic. You are not drawing to record the design; you are drawing to find out whether a design exists.


Body-plan move vs DNA move

Before any non-trivial change, name which level you are working at.

A DNA move changes how a part works without changing who owns the decision or who talks to whom. Rename a variable. Simplify a conditional. Extract a private method called from one place.

A body-plan move changes ownership, boundaries, collaborators, or the path a request takes. Extract a class. Inject a new collaborator. Move a method to where the data lives. Change what an object is allowed to know.

The label determines the process. DNA moves flow freely. Body-plan moves get surfaced, discussed, and decided deliberately, because they are the moves that reshape the creature. The most dangerous phrase in a diff is "while I was here, I cleaned it up." That sentence usually smuggles a body-plan move inside DNA work.

If you cannot decide which label a change deserves, label it up. Surface it.

Architecture, in the sense of load-bearing decisions, is not a synonym for body plan. Architecture is the load-bearing subset of the body plan. Every architectural decision is a body-plan decision; plenty of body-plan decisions (a new collaborator inside one service, a value object, a moved responsibility) never rise to architectural cost.


Refusal is design

A good object has a job and some principled ignorance. Its refusal list is what it declines to know. An object without a refusal list is not designed yet; it is merely named.

Refusal shows up at every scale. The same move, four rungs:

Product. A review-assistant product defined itself by one refusal: it suggests, never enforces. No approval writes, no merge gates, no reviewer assignment. That refusal is not brand tone; it constrains every object downstream. Build one enforcing object and you have built a different, heavier product.

Object. An app-wide callback endpoint was named for its function, not its owner, because the handler's refusal list demanded it: it is a switchboard that routes by payload type and refuses to know what any interaction means. The meaning lives in the handlers it dispatches to.

Collaborator. A service that reaches for the clock and the environment inline (Time.now, ENV[...]) knows things it never declared. Inject the clock. Inject the config. When a domain spec needs a freeze-time helper to pass, that is the object confessing a hidden dependency; injection removes the confession.

Adapter. An adapter consumed exactly the fields the domain needed from a sprawling upstream payload and stored the raw input separately. The refusal at the boundary is what keeps upstream drift out of the domain.

Where the refusal is visible, you get a joint: a designed, visible connection point where the body plan intends one part to connect to another. Constructor injection is the common form, but not the only one; a route, an adapter boundary, a policy object, an event handler can each be a joint. A joint is drawn on purpose. (Feathers' seam, a substitution point you find rather than design, is a different word for a different question; see the Canon.)

The payoff question, and the fastest review question in this chapter: "What does this object refuse to know?" If no one can answer, the object's scope is undefined and it will accumulate every nearby responsibility.

One more honesty rule while you are naming things: when a refusal guard protects against a hazard, say whether the hazard is latent or live. A hazard reachable only through legacy or raw paths is latent; guarding it is defensive hardening, not an incident fix. Framing it honestly changes the urgency, the testing, and the story you tell the team.

When refusal goes wrong

"Refusal is design" gets abused. Three bad refusals to watch for:

The god-object escalation. An object refuses every decision by pushing them all upward, until the caller decides everything. You have not designed an object; you have relocated the god object.

The hidden global. An object "refuses to know" about configuration by reaching for ENV inside a method. That is not refusal; that is a dependency hidden from the drawing. Refusing to know something means declining it visibly, at a joint.

The anemic forwarder. A tiny object that only forwards calls and adds no boundary. It refuses everything, decides nothing, and earns nothing. Deleting it makes the drawing simpler, which tells you it was never a real part.

A good refusal looks like this: accept a collaborator through a visible joint, and decline to know how it does its work.


The anatomy of tests

Sort tests by what they prove, not what type they are. Each one inspects a different part of the anatomy:

Test Proves
Acceptance test The body moves
Unit test A joint or reflex works
Contract spec Two parts fit the same socket
Fake Your code can use the socket without the real limb
Live run The outward write actually lands

Two of those need definitions. A contract spec is a test that proves socket compatibility between two parts: the real adapter and its substitute run the same examples, and CI goes red if they diverge. And the substitutes themselves come in three strengths: a fake is a working substitute, a stub is a canned answer, a mock is an expectation on an outgoing send. Use the words precisely; "mock" as a catch-all hides which part of the anatomy you are inspecting.

Two ways green lies

Gamed green. The spec builds the object differently from production, or asserts something a hard-code can satisfy. The gamed spec from earlier is the worked example, and its fix is two moves on the spec itself: construct the object the way production does, and assert behavior a hard-code cannot fake. Do both and the right design becomes the only implementation that passes.

Unverifiable green. The mirror image, hiding behind an honest red. A walking-skeleton acceptance test is supposed to be red on exactly one deferred assertion. But the runner aborts an example at its first failing expectation, so if the deferred-red assertion sits mid-example, every assertion after it never executes. The "everything else passes" claim is unverifiable. Put the honest-red assertion last, or split it into its own example. And when reading any red: one failing expectation is not evidence that the lines below it pass. They simply did not run.

Gamed green lies about what the test proves. Unverifiable green lies about what the test even ran. Guard against both when you author, and check for both when you read.

The live run

One row of the table deserves its own warning. A gateway wrote to an external tool, with complete unit coverage against a fake; the real tool was never touched by the suite. Everything the tests could prove, they proved. The one thing they could not prove is that the real write lands. One controlled live run proved it: make the change, read it back, restore the original state.

"Only a live run proves the outward write" is true, and dangerous if you generalize it casually. Tie live proof to a dry-run gate, a sandbox or test account, an idempotency key, replay protection, and a cleanup path. Especially for payment, email, certification, identity, enrollment, or anything that writes to a partner.


The rockdrill

Militaries rehearse an operation before executing it: walk the ground, in sequence, before it counts. The engineering equivalent of the rockdrill is the walking skeleton, and it is the Prove phase made concrete: the thinnest end-to-end slice, hard-coded values allowed, proving the stack is wired before any domain logic lands on it.

What the rehearsal targets is the contact surface: the genuinely-new boundary where the system first receives from, emits to, or translates for something outside itself, or across a new path. An inbound webhook. An outbound partner write. An app-wide callback sink. A new adapter, import path, or cross-system call.

Two field results shape how you use it:

Finding the contact surface collapses the work. On one integration, the design diagrams pictured a whole new subsystem. A close read found that authentication, dispatch, persistence, and the user model already existed in production. Exactly one thing was new: the inbound callback endpoint. Naming the contact surface shrank the apparent surface area to the one boundary that carried the risk, and fixed the skeleton's first target.

A feature should usually have one. The same project had a second candidate: an outward write to an external system the team did not own. It was deferred behind an explicit go/no-go decision, because that write carried auth scope, idempotency, and failure-mode cost the pilot did not need to buy yet. More than one contact surface in a feature is a scope warning. Split the feature, or defer one surface deliberately.

The rehearsal earns its keep at exactly this boundary, because that is where the tidy diagram meets reality. On that same integration, the diagrammed sequence broke at the contact surface: a short-lived token forced a synchronous call in a codebase whose convention was fire-and-forget. The rockdrill found the divergence while it was cheap. And check the boring things while you are there: on the same system, the integration's config pointed at a dead endpoint and no route existed. If the skeleton does not stand the endpoint up for real, nothing reaches the app, and no amount of beautiful unit tests will save the feature.


The pocket card

Before any non-trivial code, run seven questions:

  1. What level is this, body plan or DNA?
  2. Can I draw it?
  3. Where are the joints?
  4. What owns the decision?
  5. What does each object refuse to know?
  6. What proves the body walks?
  7. Where could the body plan be bypassed?

Question 7 is the high-skill move. The most dangerous failure is not "we forgot the pattern." It is: we followed the pattern in one place and bypassed it somewhere else. The adapter exists and the caller reaches around it. The body grew a second nervous system while every individual rule was followed.

Seven questions. Before the code, not after.


DNA makes the cell work. Body plan makes the creature coherent. Draw the creature before you write the cell.