Library · Summary & review

Domain-Driven Design

By Eric Evans. The book that named the profession's vocabulary — and how to actually use it.

FR EN
Domain-Driven Design book cover, Eric Evans

Domain-Driven Design

Domain-Driven Design: Tackling Complexity in the Heart of Software

7 /10

« Dated by Java, timeless by ideas. »

  • AuthorEric Evans
  • OriginalAddison-Wesley, 2003 · 560 pages
  • This page~14 min read
Book rating across 5 dimensionsIdeas9/10Practical6/10Readability5/10Aged well8/10Examples7/10
Architecture Design DDD

Why this book

In 2003, Eric Evans published a book he had spent years writing on projects. He was not an academic. He had spent a decade in the trenches helping teams stuck in large, tangled codebases — teams that could add features but could not make the software understand its own domain. The book was his attempt to name what was going wrong and describe what working differently looked like.

Domain-Driven Design quickly became one of the most referenced books in software development without necessarily being one of the most finished. The "blue book," as it is known, introduced a vocabulary that the entire profession ended up adopting: Ubiquitous Language, Bounded Context, Aggregate, Core Domain. Whether you know the book or not, you have probably used those words.

The ideas that stick

1Knowledge crunching: the model is born in conversation

Evans opens with a story. He is hired to build software for printed-circuit board (PCB) design. Problem: he knows nothing about electronics. The domain experts — experienced PCB engineers — know everything about circuits and nothing about software. The first meetings are discouraging. Evans tries to understand what the software should do. The engineers give him ASCII file descriptions. It goes nowhere.

Then he starts drawing diagrams during the conversations. A net — a wire conductor that can connect any number of components and carry a signal — becomes the first element of the domain model. Component instance. Pin. Topology. Probe simulation. Each conversation refines the model. Evans builds a quick prototype with no UI and no persistence, just behavior. The engineers can now see the model working and react to it. The knowledge flows in both directions.

Evans calls this knowledge crunching: the ongoing collaboration where developers and domain experts sift through a torrent of information and distill it into a model that is useful for both. He identifies five ingredients that made it work in that project:

  • binding the model to the implementation;
  • cultivating a shared language;
  • building a knowledge-rich model: not just a data schema;
  • distilling the model as understanding grows;
  • brainstorming and experimenting constantly.

Without all five, the knowledge accumulates on one side of the table and never crosses over. "Knowledge trickles in one direction, but does not accumulate." (ch. 1)

2Ubiquitous Language: one language for everyone

Evans describes the same cargo routing conversation twice. In the first version, the developer talks about "rows in the shipment table," a "Boolean flag in the Cargo object," and "deleting and regenerating" records. The domain expert says "OK, whatever" at several points. They finish the meeting and each walks away with a different understanding. In the second version, both the developer and the expert talk about a "Route Specification," an "Itinerary," and a "Routing Service that finds an Itinerary that satisfies a Route Specification." The conversation is shorter, sharper, and leaves nothing ambiguous behind.

The difference is the Ubiquitous Language: a single shared vocabulary, used in speech, in documents, and in the code itself. When the code uses the same words as the domain expert, a developer reading it immediately understands its purpose without translation. When the language drifts — when the code says "row" and the expert says "Cargo" — a translation happens at every meeting, every handoff, and every code review. Each translation loses something. Evans calls this "translation blunts communication and makes knowledge crunching anemic." (ch. 2)

Ubiquitous Language: two conversations Without Ubiquitous Language Developer Domain expert row / Boolean / flag route / Cargo / ??? constant translation, knowledge lost at every handoff With Ubiquitous Language Developer Domain expert Route Specification Itinerary / Routing Service same words in code, docs, and speech: knowledge accumulates The Ubiquitous Language lives in spoken conversations, written documents, and the code itself: it is the same language in all three places.
The same cargo routing conversation twice. Without a shared language, every exchange requires translation and loses something. With it, the words in the code match what the expert says out loud.

3The model AND the code are one thing

The classic architecture trap: the architect models in meetings, the developer codes what they can, and the two drift apart in silence. Evans calls it a deadly divide between analysis (understanding the business) and design (structuring the code): each side ends up working against the other without knowing it.

The Model-Driven Design answer is radical: the code must mirror the domain model literally. An Order object in the code maps to an order in the business, not to a table row, not to a DTO, not to an ORM (Object-Relational Mapping) entity shaped for the database. (A DTO, Data Transfer Object, is a behaviorless object whose only job is to carry data. An ORM entity is shaped by persistence concerns. Neither is a model.)

The consequence is concrete: a change in the code can be a change in the model. Renaming a class might mean changing the project's vocabulary. The code no longer describes the model from the outside, it embodies it.

Without that binding, the pathologies are predictable. The architect who doesn't code models things that can't be built. The isolated developer builds something other than what the business wanted.

Hence the Hands-on Modelers principle: everyone who shapes the model must touch the code, and every developer must have access to the domain experts. (ch. 3)

4Entity and Value Object: identity or value?

Evans was once sued for apartment damage — except he had never lived in that apartment. The landlord found his name in the phonebook, assumed he was the previous tenant, and filed a claim. Two entries on the same page of an old directory proved there were two different Eric Evanses. The point is that identity is not in the attributes. You cannot decide whether two people are the same person by comparing their name and address. Something else — a continuous thread — makes an object the same object over time, even as its attributes change. Evans calls these objects Entities.

A child drawing at a table doesn't care which marker she picks up, as long as the color and tip match. If you secretly replace the lost marker with an identical one, the drawing continues without pause. The marker has no identity worth tracking; only its attributes matter. Evans calls these objects Value Objects. They are defined entirely by their attributes, carry no continuous identity, and should be treated as immutable: if attributes change, you create a new object rather than modifying the existing one.

The difference becomes concrete the moment you write the code, and two questions settle it. Do I want to track this thing over time, so it stays "the same one" even when its values change? Then it's an Entity, compared by its identity. Or do only its values matter, two identical copies being interchangeable? Then it's a Value Object, compared attribute by attribute, and replaced rather than modified. In PHP the contrast is immediate:

// ENTITY: tracked over time, compared by IDENTITY.
final class Customer {
    public function __construct(
        private CustomerId $id,          // the identity, a small Value Object that compares itself
        private string     $name,
    ) {}
    public function rename(string $name): void {
        $this->name = $name;             // we MODIFY: still customer #1042
    }
    public function isSameAs(Customer $other): bool {
        return $this->id->equals($other->id);    // only the id matters, not the name
    }
}

// VALUE OBJECT: no identity, immutable, compared by VALUE.
final class Address {
    public function __construct(
        public readonly string $street,
        public readonly string $city,
    ) {}
    public function withCity(string $city): self {
        return new self($this->street, $city);    // we REPLACE: a brand-new object
    }
    public function equals(Address $other): bool {
        return $this->street === $other->street
            && $this->city === $other->city;      // ALL attributes matter
    }
}

And nothing is set in stone: the same object flips from one to the other depending on need. An address is a Value Object for an online shop that only wants to ship to the right place. The same address becomes an Entity for the postal service, which must track that specific mailbox over time. The question is never "what is this object by nature?" but "does my domain need to track it individually?".

A Domain Service covers operations that don't fit naturally into either: a funds transfer debits one account and credits another while applying business rules. Neither account is the right home for that logic. Evans gives three tests for a legitimate Domain Service:

  • First, the operation relates to a concept that is not a natural part of an Entity or Value Object.
  • Then, its interface is defined in terms of other elements of the domain model.
  • Finally, it is stateless — any client can use any instance without caring which instance it is.
Entity vs Value Object ENTITY Client #1042 Name: Eric Evans City: San Francisco name changes → still #1042 Defined by an identity thread. Attributes change; identity persists. VALUE OBJECT Address Street: 123 Main St City: San Francisco two equal objects are interchangeable Defined entirely by its attributes. Treat as immutable — replace, don't modify. Two bank deposits of the same amount on the same day are two Entities (both exist and matter); the amount objects they carry are Value Objects.
Same attributes, different meaning. The client keeps the same identity even when their name changes. Two identical addresses are interchangeable — replacing one with another changes nothing.

5Aggregate, Factory, Repository: managing the lifecycle

Objects in a domain model don't live in isolation. A car has wheels, a purchase order has line items, a bank account has transactions. Each of these groupings has rules that must hold across the whole group: that grouping is an Aggregate. Evans defines it as a cluster of objects treated as a single unit for data changes.

The cluster has a root: a single Entity that everything outside the boundary must reference. Internally, the Aggregate can have other Entities and Value Objects, but no external object may hold a reference to them directly, only to the root. The root is responsible for enforcing all invariants, the rules that must always stay true.

The classic example is a car in an auto-repair shop. The car is the Aggregate root, identified by its VIN (vehicle identification number). The tires are Entities inside the boundary: they have local identities (we track which tire has been on which position) but no global significance.

Nobody queries the database looking for a specific tire and then navigating to the car; they look up the car and ask it about its tires. If the tires go to the scrapyard, their identity disappears. The engine block, however, has a serial number that can be traced independently, so in some applications it would be the root of its own Aggregate.

An Aggregate's boundary Aggregate "Car" Car (ROOT) identity: VIN Tire Tire contains Outside object allowed forbidden
Outside code references only the root (the Car). The Tires live inside the boundary: you reach them through the root, never directly.

This boundary is more than protection against rogue access: it is the system's unit of consistency. Hence Evans's rule: one transaction changes a single aggregate at a time (a transaction is a group of changes that commit all or nothing). If two aggregates must move together, say confirming an Order and decrementing Stock, you don't lock them in one big shared transaction (holding two databases at once is slow and fragile). You commit the Order, then notify Stock with an asynchronous event, handled right after in its own transaction.

The payoff shows at scale. Since no transaction spans two aggregates, each can live on its own node, its own database. Updates between them travel as events. That is what makes distributed systems practical: nothing locks everything at once, each aggregate moves on its own.

One transaction per aggregate, events between them One transaction touches a single aggregate Order Aggregate Order (root) Line Line one transaction: all or nothing Stock Aggregate Stock (root) Item one transaction: all or nothing event async Between two aggregates: an asynchronous event, never a shared transaction. So each can live on its own node.
A transaction stays sealed inside a single aggregate (all or nothing). To sync two aggregates, you send an asynchronous event, which lets each live on its own machine.

A Factory handles the beginning of an object's life: assembling a complex Aggregate from its parts, enforcing invariants during creation. Evans uses the image of a car engine: the robot that assembles it has no role once you are driving. The creation logic lives in a dedicated class. The Factory knows the assembly rules, the finished object knows how to do its job, and neither knows about the other.

A Repository handles the middle and end of an object's life. It provides the illusion of an in-memory collection: instead of writing SELECT * FROM cars WHERE owner_id = ?, you call $carRepo->findByOwner($client). The SQL query disappears into the Repository, and your code thinks in objects, not tables. "A FACTORY handles the beginning of an object's life; a REPOSITORY helps manage the middle and the end." (ch. 6) Without a Repository, Evans observed that developers start thinking in SQL rows and tables rather than domain objects, and the model focus dissolves.

The three roles fit in a handful of lines. The root keeps its invariant, the Factory hands back an already-consistent object, the Repository hides the SQL:

// AGGREGATE: Car is the ROOT. The Tires live INSIDE its boundary.
final class Car {
    private array $tires = [];               // internal Entities, never exposed
    public function __construct(private Vin $vin) {}

    // outside code ALWAYS goes through the root to touch a tire
    public function changeTire(Position $pos, Tire $new): void {
        // the root enforces the invariant (one position = one tire)
        $this->tires[$pos->value] = $new;
    }
}

// FACTORY: assembles a complete, already-consistent Aggregate.
final class CarFactory {
    public function fromWorkOrder(WorkOrder $order): Car {
        $car = new Car($order->vin());
        foreach ($order->tires() as $pos => $tire) {
            $car->changeTire($pos, $tire);
        }
        return $car;                          // comes out ready to use, invariants held
    }
}

// REPOSITORY: the illusion of an in-memory collection; SQL disappears.
interface CarRepository {
    public function findByVin(Vin $vin): ?Car;       // instead of SELECT * FROM cars ...
    public function save(Car $car): void;
}

6Breakthrough: when the model jumps

Evans spent months on a software project for syndicated loans at a New York investment bank. The team had been refactoring a messy inherited codebase and had introduced a concept they called "Loan Investment" — a derived object representing an investor's proportional share of a loan.

The business experts kept saying they didn't really understand it. The team assumed that was a language gap and moved on. Rounding inconsistencies kept appearing. The design kept getting more complicated.

Then, in one week: the team realized that "Loan Investment" was not a banking term. It was something they had invented because their understanding of the domain was incomplete. The business experts had told them several times that they didn't understand it. The team had deferred to their own software knowledge and assumed it must be useful technically. It wasn't.

A new concept emerged — the Share Pie — a generic representation of any divisible value (a slice of a Facility, a slice of a Loan, a slice of a payment distribution). Loan Investment and Loan Adjustment (the other invented concept) disappeared. The model became smaller and more powerful.

"Our new model worked well. Really, really well. And we all felt sick!" — Evans is explicit that the immediate reaction to a breakthrough is not euphoria but fear. The path to the new model had no stable stopping points. Three weeks of refactoring under a deadline, no automated tests, parts of the application temporarily out of commission, a courageous manager who said yes. The usual advice — refactor in small steps, keep everything working — does not apply here. Sometimes the refactoring that matters is the kind that cannot be done incrementally.

7Bounded Context: the model has borders

On a large project, the same word covers different realities depending on who says it. In the CRM (the tool that tracks the commercial relationship), a "customer" has a purchase history and preferences. In billing, it is a legal entity with a tax ID. In support, it is an open ticket with a priority level.

Forcing a single "Customer" model on the whole system produces a monster object that satisfies no one. Worse: when the models contaminate each other in silence, you get invisible bugs, until the day the monthly report crashes on inconsistent data. Evans draws a rule: "Total unification of the domain model for a large system will not be feasible or cost-effective." (ch. 14)

A Bounded Context is the solution: an explicit boundary within which a particular model applies consistently. Inside the boundary, every term has one meaning. Outside the boundary, the same word may mean something different — and that's fine, as long as the boundary is explicit. The boundary is defined by team organization, by the codebase, and by the database schema.

Keeping that coherence inside a context takes a discipline Evans calls Continuous Integration, not to be confused with automated CI/CD pipelines. It is a team practice. When several people code in parallel, their versions of the model drift apart in silence, each refining "order" or "customer" in their own corner. You prevent it by merging often, so gaps surface early and small, and by re-aligning the language continuously, so a word keeps a single meaning in the code as in conversation.

What remains is to relate the contexts to each other: that is the job of a Context Map, a diagram (or document) that names every Bounded Context on a project and describes their relationships. Evans names ten patterns for those relationships:

  • Shared Kernel: two contexts deliberately share a subset of the model.
  • Customer/Supplier: one team formally depends on another and has a voice in its roadmap.
  • Anticorruption Layer: a translation layer that prevents a legacy or external model from bleeding into your own.
  • Separate Ways: two parts of the system with no real dependency, better off developed independently.

Evans noted that "model divergences are as likely to come from political fragmentation and differing management priorities as from technical concerns": the Context Map is as much an organizational tool as a technical one.

Context Map with two Bounded Contexts Booking Context Cargo Route Spec Itinerary Booking App team + model + DB schema Translator (Anticorruption Layer) Network Traversal Context Node Arc Graph algo different model, different team The Context Map names every model in play and describes how they relate. Integration patterns (ten in total): Shared Kernel · Customer/Supplier Anticorruption Layer · Separate Ways · and six more Each pattern describes who adapts to whom — and how much.
Two Bounded Contexts with an explicit translator between them. Each context has its own model, its own team, its own database schema. The Anticorruption Layer prevents the Network Traversal model from leaking into Booking.

8Core Domain: put the best people where it counts

On one project Evans describes, the senior developers were assigned to database infrastructure and messaging layers — technically interesting work, transferable to any résumé. The loan module — the actual heart of the business — was given to a junior developer. The result was "an incomprehensible tangle." The application never did anything remarkable, no matter how elegant the infrastructure. This, Evans argues, is a systemic failure in how teams allocate their best thinking.

The Core Domain is the part of the model that makes the application distinctively valuable. Every system also has Generic Subdomains — parts that any company in the industry would need (time zones, accounting, authentication) and that carry no competitive advantage.

Generic Subdomains are legitimate candidates for off-the-shelf solutions or temporary contractors. The Core Domain is not. "The CORE DOMAIN is where the most value should be added in your system." (ch. 15)

The Core quickly drowns in the mass of technical and generic code, until nobody knows where it is. Evans offers a ladder of techniques to make it stand out, from the cheapest to the most expensive:

  • Domain Vision Statement: one page saying what the Core is and why it matters, an hour of work that keeps a team aligned for months.
  • Highlighted Core: flag it in the existing code with comments or a README, moving nothing.
  • Segregated Core: physically move the heart into its own modules, apart from supporting code.
  • Abstract Core: extract its essential interactions into interfaces the rest depends on, the most expensive move.

Each rung makes the Core more visible. A team stops at the one its budget allows.

The Core Domain distillation ladder Distillation: make the Core stand out Domain Vision Statement one page: what the Core is (~1 h) Highlighted Core flag it in the code (comments) Segregated Core move it into its own modules Abstract Core extract its key interfaces rising effort and cost
The more you invest (longer bar), the more the Core stands out from the rest of the code. Each team stops at the rung its budget allows.

9Assessment first: six questions before strategy

Evans does not prescribe an order for applying DDD patterns. Instead, Chapter 17 opens with a diagnostic: six questions a team should answer honestly before deciding what to do next. Draw a Context Map — can you draw a consistent one, or are there ambiguous overlaps? Does the team use a Ubiquitous Language rich enough to help development? Is the Core Domain identified? Does the technology work for or against a Model-Driven Design? Do the developers have the technical skills? Are they interested in the domain?

The questions are not rhetorical. Evans warns that you cannot answer them well up front: "You know less about this project right now than you ever will in the future." (ch. 17) In other words, today is the day you know the least; you will only learn more. Architecture decisions made too early are bets in the dark. The strategic patterns are not a fixed recipe but a vocabulary for naming what is already happening on the project and spotting the moments when a team can change course.

Evans closes the book on a portrait of the kind of team DDD shapes: "They are hard to satisfy with the quality of the domain model, because they keep learning more about the domain. They see continuous refinement as an opportunity, and an ill-fitting model as a risk." (Conclusion) And he ends on the promise that justifies the whole book: software that stays "a pleasure to use and a pleasure to work on," that doesn't box us in as it grows but opens new opportunities and keeps adding value.

My honest opinion

The vocabulary alone is worth the price. Bounded Context, Ubiquitous Language, Aggregate, Core Domain — these words exist in nearly every architecture discussion in the industry now. Evans did not invent the underlying ideas, but he named them well enough that the names stuck. If you have ever heard a team debate whether something is an Entity or a Value Object, or argue about which service owns which Aggregate, you were speaking Evans' language.

The honest downsides are real. Every example is in Java — sometimes C++ — and dated Java at that (J2EE entity beans, EJB home interfaces, CORBA). Part II, which covers seventeen chapters of building blocks in FAQ format, gets repetitive fast. The technique is usually the same four moves wearing a different class name. You do not read this book cover to cover on a plane; you read the chapters that match the problem you are dealing with right now. Evans himself has said publicly that the book needs updating. The DDD Reference (a free sixty-page distillation) is the practical companion for looking things up.

Still relevant in 2026? More than ever. The Bounded Context became the natural unit of a microservice — whether or not teams knew they were doing DDD. The Core Domain and distillation patterns solve a problem that has become more acute as systems grow: how to decide where to concentrate design effort when everything looks equally important. The book that named the problem remains the best place to understand it.

Odilon

Still relevant in 2026?

The Java examples date. The mental models do not. The microservices movement quietly adopted Bounded Context as the natural unit of deployment — teams now argue about service boundaries using Evans' vocabulary without always knowing where it came from. The Core Domain and distillation patterns answer a question that has only grown sharper: in a system with a hundred moving parts, where should the best thinking go?

One thing Evans could not have predicted: AI coding assistants make it even easier to generate code that looks correct but reflects no domain model at all. The risk of shallow, model-free software has not decreased. The arguments for DDD have gotten stronger.

For whom?

Read it if

  • You are working on a system where the business logic is complex and keeps surprising you with edge cases
  • Your team and domain experts speak different languages and something always gets lost in translation
  • You are designing service boundaries and need a vocabulary beyond "microservice per resource"
  • You have read about DDD patterns online and want to understand where they actually come from

Skip it if

  • You are building CRUD apps where the domain is simple and the rules fit on a sticky note
  • The Java/C++ examples would stop you cold — start with the DDD Reference instead
  • You need a quick practical guide — this is a conceptual text, not a how-to

Go further

Evans' own Domain-Driven Design Reference is the sixty-page distillation of this book's patterns, freely available online — the place to look things up once you know the territory. Refactoring (Fowler) is the practical companion for the "how" of restructuring code toward a richer model. On the testing side, my testing course covers the harness that makes incremental model improvement safe.

Comments (0)

Browse the whole library

More book notes coming: one book at a time, the marrow only.