Domain-Driven Design (DDD) Basics
Organizing code around the real-world business concepts it models — entities, value objects, and a shared vocabulary with domain experts — rather than around technical convenience.
What is it?
Here's a common failure mode: a team builds a shipping feature, and in conversation with the warehouse manager they talk about "packages," "shipments," and "carriers." But in the code, all of that becomes generic Record objects passed around with string fields and type flags, structured however was easiest to persist to the database. Six months later, a new engineer reads the code and has no idea which fields matter, what a "shipment" is actually allowed to do, or how it differs from a "package." The business concepts got lost in translation on the way into the code.
Domain-Driven Design starts from the opposite direction: model the code around the real business concepts (the "domain"), using the exact same words the business experts use, and keep using those words consistently everywhere — in conversations, in code, in documentation. That shared vocabulary is called the ubiquitous language, and the discipline of using it consistently is meant to eliminate the translation gap between what the business means and what the code says.
Two building blocks show up constantly in DDD:
- An entity is something with a distinct identity that persists over time even as its attributes change — a specific Order with an ID is still "the same order" even after its status changes from "pending" to "shipped." - A value object has no identity of its own — it's defined entirely by its values, and two value objects with the same values are considered equal and interchangeable. A Money object representing "$20.00 USD" doesn't have an identity; another Money object with the same amount and currency is simply the same value.
DDD is a large, deep topic in full — this is a starting foothold, not the whole picture.
Explain like I'm 10
A doctor and a patient need to use the same words for the same things — 'the left knee,' not 'that joint thing.' Domain-Driven Design is insisting the code use the same precise vocabulary as the people who actually understand the business, instead of inventing its own generic, disconnected terms.
Examples
Before: generic, technically-convenient shapes
// Everything is a loosely-typed record with string flags
const shipment = {
type: "shipment",
status: "pending",
amount: 1999, // cents? dollars? unclear
currency: "USD",
};
function process(record) {
if (record.type === "shipment" && record.status === "pending") {
// ...
}
}Nothing here reflects the domain's real concepts. 'amount' as a bare number invites bugs (cents vs. dollars), and there's no code that expresses what a Shipment is actually allowed to do.
After: entities and value objects modeling the domain
class Money {
constructor(amountCents, currency) {
this.amountCents = amountCents;
this.currency = currency;
}
equals(other) {
return this.amountCents === other.amountCents && this.currency === other.currency;
}
}
class Shipment {
constructor(id, cost) {
this.id = id; // gives this object identity — two Shipments are only "the same" if the ID matches
this.cost = cost; // a Money value object, not a bare number
this.status = "pending";
}
markDispatched() {
if (this.status !== "pending") throw new Error("Only a pending shipment can be dispatched");
this.status = "dispatched";
}
}Money is a value object — two Money instances with the same amount and currency are interchangeable. Shipment is an entity — its identity (id) is what makes it 'the same shipment' even as its status changes, and its own method enforces the domain rule about when it can be dispatched.
How it works
DDD starts with conversations, not code: engineers and domain experts work out the precise meaning of terms ("what exactly counts as a 'dispatched' shipment?") and agree to use those exact terms everywhere. Those terms then become the names of classes, methods, and fields in the code — an entity for each concept with a real identity, a value object for each concept defined purely by its data, and methods on those objects that enforce the actual business rules, rather than rules scattered across service functions as loose if-checks.
Why does it exist?
It exists to close the gap between what a business genuinely means and what a codebase actually expresses — a gap that, left unaddressed, causes constant miscommunication (an engineer builds the wrong thing because "order" meant something subtly different to them than to the business) and code that's hard to trust, because business rules are scattered as ad-hoc checks instead of living in one clear, named place that matches how the business actually talks about the rule.
When to use it
DDD earns its investment in the parts of a system with genuinely complex, valuable business logic — the "core domain" a business actually differentiates on. It's most worth it when you have real access to domain experts to build the shared vocabulary with, and when the logic is complicated enough that getting the concepts precisely right matters.
When not to use it
For a simple CRUD screen with little real business logic — where the "domain" is basically "store this form and display it back" — the ceremony of entities, value objects, and a curated ubiquitous language is overhead with no real business complexity to justify it. Reserve full DDD for the parts of a system where the domain genuinely is complex.
Common mistakes
Letting the code's vocabulary drift from the business's vocabulary over time (e.g., the business starts saying 'return' but the code still says 'refund') instead of treating the ubiquitous language as something to actively maintain.
Making every object an entity with an ID, even ones that are naturally value objects (like an amount of money or an address), which adds needless identity-tracking complexity.
Putting business rules in generic service functions instead of on the entities and value objects themselves, missing much of DDD's benefit even while using its vocabulary.
Practice exercises
- Easy:
For an online bookstore, decide whether 'Book' (a specific physical copy tracked in inventory) and 'ISBN' should be modeled as entities or value objects, and justify each choice.
- Medium:
Take the loosely-typed 'shipment' example above and design a small ubiquitous-language glossary (5-6 terms) that a warehouse team and engineering team would agree on.
- Hard:
Model a 'hotel room booking' domain with at least one entity and one value object, including one business rule enforced as a method on the entity (e.g., a booking can't be modified within 24 hours of check-in).
Interview questions
What is the 'ubiquitous language' in Domain-Driven Design?
A shared vocabulary, developed together by engineers and domain experts, that's used consistently in conversations, documentation, and the code itself — eliminating translation gaps between how the business talks and how the code is written.
What's the difference between an entity and a value object?
An entity has a distinct identity that persists across changes to its attributes (two entities are 'the same' only if their identity matches); a value object has no identity of its own and is defined entirely by its data (two value objects with the same data are considered equal).
Why does DDD emphasize putting business rules on the domain objects themselves rather than in separate service functions?
So the rule lives in one place, named using the same vocabulary the business uses, and is enforced consistently wherever the object is used — rather than being reimplemented or forgotten in scattered if-checks across the codebase.