Skip to content

Domain-Driven Design

Domain-Driven Design is the practice of building software whose primary organising principle is the business domain, not the framework, not the database schema, and not the layers a generator produced.

It was named by Eric Evans in Domain-Driven Design: Tackling Complexity in the Heart of Software (2003), and made concrete for practitioners by Vaughn Vernon’s Implementing Domain-Driven Design (2013). Its claim is narrow and testable: in a domain that is genuinely complicated, the cost of change is dominated by translation — between what the business says, what the analyst wrote, what the model calls it, and what the table column is named. DDD removes the translation by refusing to have more than one language.

One model, one language, one boundary at a time. The words the domain expert uses in the room are the words in the class names, the method names, the test names and the messages on the wire. When the expert says “a voucher is redeemed”, there is a redeem method — not updateStatus(2).

That is the whole idea. Everything below is machinery for keeping it true as the system grows.

Conversations that transfer. A model expressed in the business’s own words can be checked by the business. A model expressed in OrderDataManager and processFlag cannot be checked by anyone but its author, which is why it drifts.

Change localised by meaning. When the boundaries follow the domain’s seams, a business change lands in one place. When they follow technical layers, a business change lands in all of them — controller, service, DTO, entity, mapper — which is the everyday experience most teams mistake for normal.

Invariants that have an owner. “An order’s total is the sum of its lines” is either enforced by one object that cannot be bypassed, or re-implemented in every caller until two of them disagree. DDD’s tactical patterns exist mostly to give each rule exactly one home.

A vocabulary that survives handover. The ubiquitous language outlives the people, the framework and usually the database. It is the part of the system that is actually worth documenting.

It is not free and it is not universal. The modelling conversations are real calendar time; the tactical patterns add types and indirection; and a team that adopts the vocabulary without the conversations gets the cost with none of the benefit — Aggregate suffixes over a CRUD application.

The honest rule: DDD pays where the domain is the hard part. If the hard part is throughput, or the domain is “store this form and show it again later”, the translation cost DDD removes was never the cost you were paying.

DDD is two practices that share a name, and confusing them is the most common way teams get it wrong — a team doing tactical patterns inside a boundary nobody ever drew has bought the syntax of DDD and none of the benefit.

Strategic DDD Tactical DDD
Question What are the models, where do they end, and how do they talk? Inside one model, what shape does the code take?
Scale The system, and the organisation around it One bounded context, one package tree
Decided by Architects, domain experts, product — in a room The developer, while writing the code
Artefacts Subdomains, bounded contexts, a context map, a glossary Value objects, entities, aggregates, domain events, repositories
Changed Rarely, and expensively Continuously, at refactor cost
Fails as One enormous model everyone edits and nobody owns An anaemic model: data classes plus a service that does everything

Both halves are needed and they fail in opposite directions. Strategic work without tactical work produces beautifully bounded contexts full of getters and setters. Tactical work without strategic work produces exquisite aggregates inside a boundary drawn by a database schema.

Ubiquitous language. One language per bounded context, used by everyone, written down as a glossary that is allowed to change. The same word may mean different things in two contexts, and that is a feature — the mistake is forcing them into one shared definition.

Subdomains. Core is what the business competes on; build it, staff it well, and this is where DDD earns its cost. Supporting is necessary and specific; build it plainly. Generic is solved (billing, identity, notifications); buy it, and do not model it.

Bounded contexts. The boundary inside which one model and one language hold. This is the unit that becomes a module, a deployable, or a team’s ownership — and it is exactly what event storming is looking for when it hunts for seams.

Context mapping. How two contexts relate, named honestly: partnership, shared kernel, customer / supplier, conformist, anticorruption layer, open host service, published language, separate ways. The names are political as much as technical, which is why writing the map down usually starts an overdue conversation.

The seams that come out of this are what the C4 model draws and what the event catalogue registers, so the boundary stops being folklore.

The tactical half — where the developer acts

Section titled “The tactical half — where the developer acts”

This is the half that changes what you type every day, and it has its own page in the pattern catalogues:

Tactical DDD patterns — value objects, entities, aggregates, domain events, repositories, domain services, factories and specifications, with the naming and package-structure rules that follow from them.

It lives in code design rather than here because that is what it is: a pattern catalogue, sitting alongside GoF, architecture, integration and use case patterns, and consumed at the same moment as those — while the code is being written.

DDD is not a stage in the chain; it is the language the rest of the chain is conducted in.

Practice What DDD contributes
Event storming The workshop DDD’s strategic half is usually discovered in — its clusters are candidate bounded contexts
Example mapping Rules are candidate invariants; the words on the cards are the ubiquitous language being drafted
BDD Given / When / Then written in the ubiquitous language — the same nouns and verbs as the code
ATDD The acceptance test names the domain behaviour, so it survives every refactor that does not change behaviour
Contract-first A published language at a context boundary is a contract; an anticorruption layer is what protects you from someone else’s
Code design The tactical patterns, and the naming rules that make a package tree readable as a domain

The test of whether it is working is cheap to apply: read a class name aloud to a domain expert. If they recognise it, the language is ubiquitous. If you have to translate, it is not — and the translation you just performed is the cost DDD was supposed to remove.

  • Tactical patterns, no boundary. Aggregates and value objects inside a single model spanning the whole company. The patterns cannot save a model that has no edge.
  • The anaemic domain model. Entities with fields and accessors, all behaviour in a Service. This is the default outcome of applying the vocabulary without the modelling, and it is described in detail on the tactical patterns page.
  • DDD on a generic subdomain. Modelling an invoicing engine you could have bought. The cost is real and the competitive return is zero.
  • The glossary as a deliverable. A language nobody speaks in the standup is not ubiquitous, it is a document. It has to be the words in the code, or it is not doing anything.
  • One database, many contexts. Two models sharing tables are one model with two names. The boundary is wherever the schema says it is.

Written. The strategic half is summarised here on purpose — the decisions it produces land in the C4 model and the event catalogue rather than in this hub. The tactical half is where the developer acts, and it is written out with examples in tactical DDD patterns.