System Design · Architecture

Domain-Driven Design

Modelling the business the way the business describes it, and drawing boundaries where the language changes.

— min read System Design

The Model Is The Point

Domain-driven design is the claim that the hard part of most software is understanding the business, and that the code should therefore be organised around that understanding rather than around technical layers.

The ubiquitous language is where it starts: the same words in conversation, in the model, in the code and in the database. When a domain expert says "a policy lapses", there is a Policy with a lapse() operation — not a StatusUpdateService setting a flag to 3.

Translation is where knowledge is lost. Every time a business term is renamed on its way into code, a future reader has to rediscover the mapping, and the two vocabularies drift until nobody can review a rule against the requirement.

Entities, Value Objects & Aggregates

ConceptIdentityExample
EntityHas an id; two with the same fields are still differentCustomer, Order
Value objectDefined entirely by its values; interchangeableMoney, DateRange, Address
AggregateA cluster with one root that guards its invariantsOrder with its lines
RepositoryLoads and stores whole aggregatesOrderRepository
Domain eventSomething the business cares that happenedOrderPlaced
# a value object: immutable, compared by value, and it protects its own rules
@dataclass(frozen=True)
class Money:
    amount: int          # minor units — never a float
    currency: str

    def __add__(self, other: "Money") -> "Money":
        if self.currency != other.currency:
            raise ValueError("cannot add different currencies")
        return Money(self.amount + other.amount, self.currency)

The aggregate is the idea that earns its place fastest. It defines a consistency boundary: everything inside is saved together and its invariants always hold, and anything outside is referenced by id and updated separately. That single rule answers most "should this be one transaction" questions.

Large aggregates are the classic mistake. An aggregate covering a customer, their orders and their invoices means every change locks all of it. Keep them small — one root, the data its rules genuinely need, everything else by id.

Bounded Contexts

The strategic half, and the more valuable one. A bounded context is a boundary within which a model and its language are consistent. "Customer" means something different to billing, to support and to marketing — forcing one shared definition produces a model that serves none of them.

Context"Customer" means
SalesA prospect with a pipeline stage
BillingA payment method and a tax jurisdiction
SupportA person with a contact history and entitlements
ShippingAn address and delivery preferences

Context mapping then names the relationships: which context is upstream, who conforms to whose model, and where a translation layer sits between them. An anti-corruption layer is the pattern that keeps a legacy or supplier model from leaking into a clean one.

Bounded contexts are the most defensible basis for service boundaries. Splitting services by technical layer or by team convenience produces chatty, coupled services; splitting where the language changes produces ones that can actually evolve independently.

When It Is Worth It

Suits DDDDoes not
Complex, contested business rulesCRUD over forms
A long-lived system with real domain expertsA short-lived internal tool
Language that differs across departmentsOne obvious shared model
Rules that change more than the technologyTechnology that changes more than the rules
The tactical patterns — entities, value objects, aggregates — are cheap and useful almost anywhere. The strategic work of context mapping and an anti-corruption layer costs real time and only pays back where the domain is genuinely complicated.

Applied to a simple CRUD application, DDD produces four layers of ceremony around a database table. That is the criticism people make of it, and on those systems they are right.

Interview Questions

What is the ubiquitous language?

One vocabulary shared by domain experts, the model and the code. It removes the translation step where knowledge is lost and the two vocabularies drift apart.

Entity or value object?

An entity has identity that persists through change; a value object is defined entirely by its values and is interchangeable. Money and date ranges are value objects; a customer is an entity.

What does an aggregate define?

A consistency boundary: one root, invariants that always hold, saved as a unit. Anything outside it is referenced by id and updated in a separate transaction.

Why keep aggregates small?

A large aggregate means every change locks and loads all of it, creating contention and slow writes. Small aggregates with id references keep transactions narrow.

What is a bounded context?

A boundary within which one model and one language are consistent. "Customer" legitimately means different things to billing and support, and each context keeps its own definition.

When is DDD the wrong choice?

On CRUD applications and short-lived tools with a simple shared model. The strategic patterns cost real time and only repay where the domain rules are genuinely complex and contested.

Quick Quiz

1. A value object is compared by…
2. An aggregate defines a…
3. Large aggregates cause…
4. A bounded context is bounded by…
5. An anti-corruption layer protects against…