Skip to content

03 · Bind model to code

When you finish this lesson you should be able to:

  • Detect anemic domain “services” that hide the model in JSON and scripts
  • Refuse a design that cannot survive contact with transactions, identity, and persistence
  • Let the product’s UI speak the model’s words without dumping the schema
  • Keep the people who model close to the people who ship (often the same people)

Model-driven design is a binding, not a phase

Section titled “Model-driven design is a binding, not a phase”

“Model then implement” is the failure mode this chapter exists to kill. A model that is only a drawing forks the day coding starts. Developers invent a second, implicit model that actually runs. Experts keep talking to the drawing. You now have two ubiquitous languages.

Model-driven design: the software’s structure reflects the model. When insight changes, the code changes. The names in the domain layer are the language from lesson 02.

This is iterative: code teaches you what the model cannot yet say (you cannot traverse that association without loading the world; that invariant cannot be transactional). That pain goes back to the next conversation with experts. Implementation constraints are part of modeling, not a betrayal of purity.

Objects are the book’s home paradigm because they can carry both data and rules. You can do model-driven design in other styles, but you still need a place where the language lives.

Tools (ORM, “entity” folders, codegen, admin CRUD) are useful until they force a worse model:

  • 1:1 table mapping is persistence, not automatically a domain model.
  • Inheritance dumped into the database for two similar screens will haunt you.
  • If the tool cannot express a value object without an id, you will accidentally create entities.

A strong engineer treats the tool as infrastructure (lesson 04) and keeps the model honest even when the mapper wants a surrogate key on QuietHours.

Evans wants people who can whiteboard with an accountant and write Java with a programmer—in 2003’s terms: the modeler is not a separate caste.

If analysts never feel:

  • eager graphs that explode memory
  • circular associations
  • “save the world” transactions
  • eventual consistency across stores

…they will draw models no runtime can keep consistent.

If coders never sit with experts, they will encode the database and call it the domain.

On a small team this means: the person negotiating Holdout also writes inHoldout. On a larger team it means: no model change ships without someone who touched both the conversation and the code.

Users should not need your class diagram. They should meet the concepts. If the work has Assignments and Holdout, the product can say those words. People then make fewer impossible requests (“just show everyone the treatment, but keep holdout intact, but don’t recompute”).

When the UI invents a third vocabulary (“buckets,” “slots,” “flavors”), you have three languages again.

Bones showing is not leaking storage. It is leaking meaning in a controlled way.

Disconnected: a 12-box Miro of “experiment platform,” plus ExperimentService.updateConfig(blob). Tests assert substrings. Nobody can review a PR for a business rule.

Bound:

function rotateSalt(experiment, now) {
return { ...experiment, salt: newSalt(), saltRotatedAt: now };
}
function assignmentOf(experiment, userContext) {
return {
experimentId: experiment.id,
userId: userContext.id,
variation: pickVariation(experiment, userContext),
saltUsed: experiment.salt,
};
}

Product copy: “Rotate salt (invalidates assignments).” A test: assignments computed with the old salt do not match. The Miro, if it exists, uses rotateSalt and Assignment.

When the platform cannot atomically update millions of stored assignments, that constraint returns to the model: Assignment is recomputed, not stored as gospel—or Assignment is an entity with history because legal needs last-Tuesday. That choice is modeling because of implementation. Lesson 06 will make the aggregate explicit; here you only need to see that hiding the constraint in a comment is how the binding breaks.

  • The spec is the model. 40 pages. Code is a bag of procedures. After six months nobody trusts either.
  • The framework is the model. Everything is an Entity because Spring/Django said so. Identity leaks into values.
  • The architect owns the model, the team owns the tickets. Divergence in a sprint.
  1. Pick a module that “does” experiments, orders, or onboarding.
  2. List names that would confuse an expert. Rename three on paper to match speech.
  3. Find a procedure that should be a verb on an entity (rotateSalt, markVerified, snooze).
  4. Write the signature. Count ids. More than two or three unrelated ids means you are threading a script, not a model.
  5. Write one constraint the runtime imposes (no distributed transaction, 100ms budget). Say how the model should change to stay honest, rather than pretending the diagram is still true.
  • Domain-Driven Design — Chapter 3, Binding Model and Implementation (model-driven design; tools; bones showing; hands-on modelers)