Domain-Driven Design
Tactical and strategic patterns for software where domain complexity is the central challenge. Evans' canonical text distilled into actionable design guidance.
- › Apply Entity, Value Object, Aggregate, Repository, and Factory tactical patterns
- › Design Bounded Contexts and Context Maps for large-scale systems
- › Develop Ubiquitous Language collaboratively with domain experts
- › Choose integration patterns: Shared Kernel, Anticorruption Layer, Open Host Service, Conformist
- › Identify and protect the Core Domain from generic subdomains
- › Recognize anti-patterns: Anemic Domain Model, Smart UI, Overambitious Unification
- › Apply Supple Design patterns: Intention-Revealing Interfaces, Side-Effect-Free Functions
- › Structure large systems using Responsibility Layers and Knowledge Level
Install this skill and Claude can identify Bounded Contexts, design Aggregates with correct invariant boundaries, apply Repository and Factory patterns, detect Anemic Domain Models, and recommend Anticorruption Layer placement from a description of your domain or codebase
Code that diverges from how the business thinks about its domain becomes progressively harder to maintain and extend — DDD gives Claude a principled framework for closing that gap, transforming it into an architecture collaborator who catches boundary violations and model corruption before they compound
- › Reviewing a microservices PR for context boundary violations and suggesting Anticorruption Layer placement where one service's model would corrupt another
- › Describing an e-commerce order domain so Claude can propose Aggregate boundaries, identify root Entities, and explain which invariants justify keeping objects inside versus outside each boundary
- › Presenting an existing monolith so Claude can draw a Context Map of what currently exists, identify the Core Domain, and outline a step-by-step DDD migration strategy
Domain-Driven Design Skill
Core Philosophy
Domain-Driven Design (DDD) is a set of principles and patterns for building software where the central focus is the domain and domain logic. The core thesis: the most significant complexity in most software projects is not technical — it is in the domain itself. Software that lacks a good domain model becomes a system that “does useful things without explaining its actions.”
Five foundational principles:
-
The domain model is the heart of the software. The model is not a diagram or artifact — it is the set of concepts built up in the heads of people on the project, with terms and relationships that reflect domain insight. The model must be directly reflected in code.
-
Model and implementation are bound together. If the design, or some central part of it, does not map to the domain model, that model is of little value, and the correctness of the software is suspect. There must be no separate “analysis model” that diverges from the code.
-
Knowledge crunching is the engine. Effective domain modelers are knowledge crunchers — they take a torrent of information and probe for the relevant trickle. They try one organizing idea after another, searching for the simple view that makes sense of the mass. Models are tried, rejected, or transformed.
-
Language is the primary tool. A shared Ubiquitous Language between developers and domain experts — used in code, diagrams, and especially speech — is the most powerful instrument for building correct software.
-
Iterative deepening, not upfront completeness. Deep models emerge through continuous refinement. “Breakthroughs” — sudden shifts to fundamentally better models — come after sustained, disciplined iteration. You cannot know at the start where the model will end up.
Key Patterns & Concepts
Tactical Patterns (Building Blocks)
Entities (Reference Objects)
Objects defined by a thread of continuity and identity, not by their attributes. An Entity persists through state changes and even across different implementations.
- When to use: When you need to track something through different states, and the question “is this the same thing?” matters. A Person, an Order, a Bank Account.
- Design guidance: Strip the Entity’s definition down to the most intrinsic characteristics — particularly those that identify it or are commonly used to find or match it. Separate other characteristics into associated objects. Define a means of distinguishing each object regardless of its form or history.
- Identity strategies: Natural keys from the domain (SSN, ISBN), generated unique IDs (UUIDs, sequences), or composite keys. The choice should emerge from the model and how domain experts think about identity.
Value Objects
Objects that describe a characteristic or attribute with no conceptual identity. We care about what they are, not which one they are.
- When to use: Colors, addresses, money amounts, date ranges, coordinates. If two instances with the same attributes are interchangeable, it is a Value Object.
- Design guidance: Treat as immutable. Operations on Value Objects should return new instances rather than modifying existing ones. This makes them safe to share, copy, and pass freely. Eliminate all bidirectional associations between Value Objects. Allow optimizations like sharing and flyweight patterns.
- Key insight: Value Objects are not just “dumb data holders.” They should contain domain logic relevant to the values they represent (e.g., a Money value object knows how to do currency conversion).
Services
Operations that belong to the domain but don’t naturally fit as a responsibility of an Entity or Value Object. They represent activities or actions, not things.
- Three characteristics of a good Service:
- The operation relates to a domain concept not naturally part of an Entity or Value Object
- The interface is defined in terms of other elements of the domain model
- The operation is stateless
- Layer distinction:
- Application Services: Coordinate tasks, delegate to domain objects. Thin. No business rules. Example: “Transfer funds between accounts” (orchestration).
- Domain Services: Embody business rules that span multiple objects. Example: “Calculate transfer fees based on account types and amounts.”
- Infrastructure Services: Purely technical. Example: “Send email notification.”
- Warning: Don’t let Services strip Entities and Value Objects of all their behavior. A “Transaction Manager” that does everything while domain objects are passive data containers is an anti-pattern.
Aggregates
A cluster of associated objects treated as a unit for data changes. Each Aggregate has a root Entity and a boundary.
- Rules:
- The root Entity has global identity; internal Entities have local identity only
- Nothing outside the boundary can hold a persistent reference to anything inside except the root
- Only Aggregate roots can be obtained directly with database queries; all other objects must be found by traversal
- A delete operation must remove everything within the boundary at once
- When any change within the boundary is committed, all invariants of the whole Aggregate must be satisfied
- Design guidance: Invariants that must be maintained with every transaction define the Aggregate boundary. Rules that span Aggregates are not expected to be always up-to-date — they can be resolved through eventual consistency. Choose boundaries based on true business invariants, not just conceptual grouping.
- Example: A Purchase Order is an Aggregate root; its Line Items are inside the boundary. The Part (product) referenced by a Line Item is a separate Aggregate root. Price changes to Parts don’t automatically propagate to existing PO Line Items — the Line Item holds its own copy of the price.
Repositories
Provide the illusion of an in-memory collection of all objects of a certain type. They encapsulate the actual storage mechanism.
- Purpose: Reconstitute existing objects from persistent storage. Provide query interfaces that reflect domain needs, not database structure.
- Design guidance: Define Repositories only for Aggregate roots. The interface should be based on the Ubiquitous Language (e.g.,
findOutstandingInvoicesFor(Customer)notSELECT * FROM invoices WHERE status = 'outstanding' AND customer_id = ?). Clients should remain oblivious to whether objects are in memory or being reconstituted from storage. - Relationship to Factories: Factories create new objects; Repositories find existing ones. A Repository may use a Factory internally to reconstitute objects from stored data.
Factories
Encapsulate the knowledge needed to create complex objects or Aggregates.
- When to use: When construction of an object or Aggregate is complicated, involves enforcing invariants, or reveals too much of internal structure.
- Requirements:
- Each creation method is atomic and enforces all invariants of the created object or Aggregate
- The Factory should be abstracted to the type desired, not the concrete class
- Placement: Factory Methods on the Aggregate root for creating internal objects. Standalone Factories for creating entire Aggregates. Repositories use Factories internally for reconstitution.
Domain Events (implicit in Evans, formalized by community)
Something that happened in the domain that domain experts care about. Evans discusses making implicit concepts explicit, and events are a key category of these.
Modules (Packages)
Modules are part of the model and should reflect concepts in the domain, not just technical decomposition.
- Guidance: High cohesion within modules, low coupling between them — but applied to concepts, not just classes. Module names should be part of the Ubiquitous Language. Choose modules that tell the story of the system at a high level.
Specification Pattern
A predicate-like Value Object that tests whether another object satisfies specified criteria. Extracts complex boolean logic from Entities and into explicit, combinable rule objects.
- Three uses: Validation (does this object satisfy the spec?), Selection/Querying (find all objects matching the spec), Building-to-order (create something that satisfies the spec).
- Combinability: Specifications can be composed with AND, OR, NOT operations to build complex rules from simple ones.
- Example:
InvoiceDelinquencySpecification.isSatisfiedBy(invoice)instead of burying delinquency rules inside the Invoice class.
Strategic Patterns (Model Integrity)
Bounded Context
The most fundamental strategic pattern. A Bounded Context defines the range of applicability of a particular model. Within a Bounded Context, the model is kept unified and consistent. Different Bounded Contexts may have different models that use the same terms with different meanings.
- Key insight: Total unification of the domain model for a large system is not feasible or cost-effective. Multiple models are inevitable. The question is whether they are managed explicitly or left to cause confusion.
- Boundary markers: Team organization, codebase boundaries, database schemas, and the applicability of the Ubiquitous Language all define and signal context boundaries.
- Common problems when contexts are not explicit:
- Duplicate concepts: Two model elements representing the same concept, diverging over time
- False cognates: Same term used for different concepts (e.g., “Charge” meaning different things in billing vs. payments)
Context Map
A global view of all Bounded Contexts on a project and the relationships between them. It maps the existing terrain — what IS, not what should be.
- Purpose: Make boundaries and relationships explicit so everyone on the team understands them. Name each Bounded Context and make names part of the Ubiquitous Language.
- How to create: Identify each model in play. Define its Bounded Context. Describe points of contact between models. Highlight sharing, translation, and communication mechanisms.
- Rule: Don’t change the map until the change in reality is done. First describe what IS, then plan changes.
Continuous Integration
Within a Bounded Context, keep the model unified through:
- Constant exercise of the Ubiquitous Language in discussions
- Frequent merging of all code and implementation artifacts
- Automated test suites to flag fragmentation quickly
- A shared understanding of the evolving model among all team members
Shared Kernel
Two teams agree to share a subset of the domain model (including code and database design). This shared subset has special status and cannot be changed without consultation. Both teams run each other’s tests against it.
- When to use: Teams working on closely related functionality where some unification is valuable but full Continuous Integration is too expensive.
- Trade-off: Less freedom to change independently, but reduced duplication and easier integration.
Customer/Supplier Development Teams
For upstream/downstream relationships where one subsystem feeds another. The downstream team plays the customer role in the upstream team’s planning process.
- Key elements:
- The downstream team’s needs are treated as real customer priorities (not “poor cousin” status)
- Jointly developed automated acceptance tests validate the interface
- The tests are critical: They let the upstream team change freely without fear of breaking downstream, and free the downstream team from constantly monitoring upstream changes.
Conformist
When the upstream team has no motivation to accommodate the downstream team’s needs, the downstream team may choose to slavishly adopt the upstream model.
- When to use: When the upstream model is decent quality and integration is essential, but you have no leverage to influence it. Common with off-the-shelf components with large interfaces.
- Trade-off: Cramps downstream design, but massively reduces translation complexity.
Anticorruption Layer
An isolating translation layer that protects your model from being corrupted by a foreign model (legacy system, external service, or poorly designed upstream).
- Structure: Composed of Facades (simplifying the external interface), Adapters (converting protocols), and Translators (converting conceptual objects between models).
- When to use: When you must integrate with a system whose model would corrupt yours if allowed to leak through. Especially important with legacy systems.
- Key insight: This is not just data format translation. It translates between entire conceptual models — different ways of thinking about the domain.
Open Host Service
When a subsystem must be integrated with many others, define a protocol that gives access as a set of Services. Open the protocol so all integrators can use it.
- When to use: When your subsystem is in high demand and customizing a translator for each consumer is too expensive.
- Guidance: Keep the protocol simple and coherent. For teams with idiosyncratic needs, use a one-off translator to augment the protocol rather than polluting it.
Published Language
A well-documented shared language (e.g., an industry-standard schema, an interchange format) used as the common medium of communication between Bounded Contexts.
- When to use: When direct translation between two models is awkward or when multiple parties need to exchange domain information. XML schemas, JSON schemas, Protocol Buffers, and industry-standard formats are all implementations of this pattern.
- Relationship to Open Host Service: A Published Language often serves as the model for an Open Host Service’s protocol.
Separate Ways
When two sets of functionality have no significant relationship, cut them loose from each other entirely.
- When to use: When integration provides no significant benefit, or the cost exceeds the value. Just because features appear in the same use case doesn’t mean they must be integrated.
- Caution: Forecloses future integration options. Models developed in complete isolation are hard to merge later.
Distillation Patterns
Core Domain
The most valuable, most specialized part of the model — the part that differentiates the application and makes it a business asset.
- Guidance: Boil the model down. Make the Core small. Apply top talent to it. Invest in deep modeling and supple design for the Core. Make everything else as generic as practical.
- Warning: Scarce, highly-skilled developers tend to gravitate toward technical infrastructure or neatly definable problems, leaving the Core to less experienced developers. This must be actively countered.
- The Core Domain cannot be purchased or outsourced. Creating distinctive software requires a stable team accumulating specialized knowledge. No shortcuts.
Generic Subdomains
Parts of the model that are necessary but not distinctive to the business. They add complexity without capturing specialized knowledge (e.g., organizational hierarchies, accounting models, time/date handling).
- Options: Use off-the-shelf solutions, outsource, implement with minimal investment. Don’t let these distract from the Core.
Domain Vision Statement
A short document (about one page) that describes the Core Domain and the value proposition. Not a complete design document — a focused statement of direction and priorities.
Highlighted Core
A document of 3-7 pages that describes the Core Domain’s most essential elements: the core concepts, their interactions, and the primary scenarios. Alternatively, flag the Core Domain elements in the code itself.
Cohesive Mechanisms
Algorithms or complex computations that can be separated into their own module with an Intention-Revealing Interface, freeing the domain model from mechanical complexity.
Segregated Core
Refactor the Core Domain into a separate module (or set of modules), reducing its coupling to supporting elements and making the Core visible and manageable.
Abstract Core
Factor the most fundamental concepts and their relationships into a set of abstract classes or interfaces, moving detailed specializations into separate modules.
Supple Design Patterns
Intention-Revealing Interfaces
Name classes and operations to describe their effect and purpose, not how they work. The client developer should never need to read implementation to use an object correctly.
- Test: Write a test for the behavior before implementing it, forcing yourself into client-developer mode.
- Rule: In public interfaces, state relationships and rules, but not how they are enforced. Describe events and actions, but not how they are carried out. Pose the question but don’t present the means to find the answer.
Side-Effect-Free Functions
Separate query/calculation logic into functions that return results without modifying observable state. Keep commands (state-changing operations) segregated and as simple as possible.
- Strategy: Move complex logic into Value Objects, which are naturally immutable. Operations on Value Objects return new Value Objects, making them safe to compose and test.
- Impact: Dramatically reduces the cognitive load of understanding what code does. Functions can be called multiple times, composed freely, and tested trivially.
Assertions
State post-conditions of operations and invariants of classes and Aggregates explicitly. Even if your language doesn’t support formal contracts, express them in tests, documentation, or naming.
- Purpose: Developers can understand the consequences of using an operation without reading its implementation. This makes encapsulation and abstraction safe.
Conceptual Contours
Decompose design elements to align with the natural joints in the domain — the stable, meaningful divisions that domain experts recognize.
- Guidance: When elements of a model are embedded in a monolithic construct, functionality gets duplicated and meaning is obscured. When broken too finely, the pieces lose coherent meaning. Find the granularity that matches the domain’s own contours.
Standalone Classes
Aim for low coupling at the individual class level. Every dependency is a cognitive burden. The ultimate ideal: a class that can be understood entirely on its own.
Closure of Operations
Where possible, define operations whose return type is the same as the type of its argument(s). This creates a closed set of operations that can be chained and composed (e.g., mathematical operations on Value Objects).
Large-Scale Structure Patterns
Evolving Order
Let the large-scale structure evolve with the application rather than being imposed upfront. An ill-fitting structure is worse than none.
- Guidance: Use structure only when it genuinely clarifies. Less is more. A large-scale structure should be applicable across Bounded Contexts without over-constraining local design decisions.
Responsibility Layers
Organize the domain model into layers based on broad domain responsibilities (not technical layers). Common layers in enterprise systems:
- Potential/Capability: What could the organization do? (Resources, capabilities, capacities)
- Operations: What is happening? What is the current state? (Day-to-day activities, tracking)
- Decision Support: What should be done? (Analysis, planning, recommendations)
- Policy: What are the rules? What are the goals? (Business rules, constraints, objectives)
- Commitment: What have we promised? (Contracts, obligations, agreements)
Knowledge Level
A group of objects that describe how another group of objects should behave — essentially, a model of the model. Separates configuration/rules that users can customize from operational data.
- When to use: When the structure and behavior of the operations-level objects needs to be flexible and configurable without code changes.
Pluggable Component Framework
An extremely mature pattern where interfaces are defined so rigorously that components can be substituted freely. Requires deep understanding and highly distilled models. Rarely achieved; don’t attempt prematurely.
Decision Frameworks
When to Use Which Tactical Pattern
| Situation | Pattern |
|---|---|
| Object tracked through lifecycle, identity matters | Entity |
| Descriptive attribute, identity irrelevant, interchangeable | Value Object |
| Operation spanning multiple objects, no natural home | Domain Service |
| Cluster of objects with shared invariants | Aggregate |
| Complex object creation, multiple invariants to enforce | Factory |
| Finding/retrieving persistent objects | Repository |
| Boolean business rule, combinable conditions | Specification |
| Coordinating workflow, no business logic | Application Service |
When to Use Which Integration Pattern
| Situation | Pattern |
|---|---|
| Close collaboration, shared ownership | Shared Kernel |
| Clear upstream/downstream, cooperative teams | Customer/Supplier |
| Upstream won’t cooperate, their model is acceptable | Conformist |
| Upstream won’t cooperate, their model would corrupt yours | Anticorruption Layer |
| Your subsystem serves many consumers | Open Host Service |
| Need a standard interchange format | Published Language |
| No meaningful relationship between systems | Separate Ways |
Aggregate Boundary Decision Guide
- What are the true invariants that must be enforced transactionally? These define the Aggregate boundary.
- What is the frequency of concurrent access? High-contention objects should be in smaller Aggregates.
- What are the consistency requirements? Same-transaction consistency needs go inside the Aggregate. Eventually-consistent relationships go between Aggregates.
- When in doubt, prefer smaller Aggregates. Large Aggregates cause concurrency problems and unnecessary coupling.
Anti-Patterns
Evans warns explicitly or implicitly about these mistakes:
-
Analysis Model Divorced from Implementation. Creating a beautiful domain model that lives on paper while developers build ad hoc code. The model must live in the code or it is worthless.
-
Anemic Domain Model. Entities that are nothing but data containers, with all logic in “Manager” or “Service” classes. This negates the point of object-oriented domain modeling.
-
Smart UI Anti-Pattern. Embedding business logic directly in the user interface. Acceptable only for very simple applications that will never grow. Incompatible with DDD.
-
Overambitious Unification. Attempting to force a single model across an entire enterprise. This leads to bloated, compromised models that serve no one well. Accept multiple Bounded Contexts.
-
Infrastructure-First Development. Best developers working on frameworks, persistence layers, and messaging while junior developers build the Core Domain. Invert this allocation.
-
Implicit Concepts Left Buried. Business rules hidden as guard clauses, constraints embedded in procedural code, processes implicit in method sequences. Make these explicit model elements (Specifications, Policies, domain Services).
-
Leaky Abstractions Between Contexts. Allowing the model of one Bounded Context to bleed into another without a deliberate translation layer. This leads to false cognates and duplicate concepts.
-
Premature Generalization. Building frameworks and abstract layers before understanding the domain deeply enough. “A lot of over-engineering has been justified in the name of flexibility.”
-
Ignoring the Ubiquitous Language. Technical jargon in code that domain experts cannot recognize. If domain experts say your model is “too technical for them,” your model is probably wrong.
-
Fear of Breakthroughs. Avoiding large model refactoring because the short-term cost seems too high. The breakthrough to a deeper model is often the most valuable thing a project does — delaying it only makes it harder.
Vocabulary
Ubiquitous Language: A shared language between developers and domain experts, used in all communication and in the code itself. Changes to the language are changes to the model.
Model-Driven Design: A design where the software structure reflects the domain model, and changes to the model are directly reflected in code.
Knowledge Crunching: The collaborative process of distilling domain knowledge into a useful model through brainstorming, experimentation, and iterative refinement.
Bounded Context: The explicit boundary within which a particular domain model applies and is kept consistent.
Context Map: A visualization of all Bounded Contexts and their relationships on a project.
Aggregate: A cluster of domain objects treated as a unit for data changes, with a root Entity controlling access.
Entity: An object defined by identity and continuity, not by attributes.
Value Object: An immutable object defined entirely by its attributes, with no conceptual identity.
Domain Service: A stateless operation that represents a domain concept but doesn’t belong to any Entity or Value Object.
Repository: An object that encapsulates data access and provides the illusion of an in-memory collection.
Factory: An object responsible for creating complex domain objects while enforcing invariants.
Specification: A predicate Value Object that encapsulates a business rule as a boolean test.
Anticorruption Layer: A translation layer that protects a model from corruption by a foreign model.
Core Domain: The distinctive, highest-value part of the model that differentiates the business.
Generic Subdomain: A part of the model that is necessary but not distinctive — not a competitive advantage.
Supple Design: A design that is both easy to use (for client developers) and easy to change (for maintainers), achieved through patterns like Intention-Revealing Interfaces and Side-Effect-Free Functions.
Deep Model: A model that goes beyond surface-level understanding to capture the fundamental concepts and relationships that domain experts recognize as essential.
Breakthrough: A sudden, often dramatic improvement in the model that emerges after sustained iteration — shifting the entire conceptual framework to one that is simpler, more powerful, and more aligned with the domain.
Layered Architecture: The four conceptual layers: User Interface, Application, Domain, and Infrastructure. The Domain layer is the heart of business software and must be isolated from the others.
Application Guidelines
Starting a New Project with DDD
- Engage domain experts from day one. Developers and domain experts must collaborate directly — no intermediaries who “translate.”
- Establish a Ubiquitous Language immediately. Start modeling out loud: talk through scenarios using candidate model terms. Listen for rough edges — they reveal model problems.
- Bind model to implementation early. Build a crude prototype that exercises the model in code, even with dummy data and console output. This forces precision and provides feedback.
- Isolate the domain layer. Use Layered Architecture to keep domain logic free from UI, persistence, and application concerns.
- Start with Aggregates and Entities. Identify what has identity and lifecycle. Define Aggregate boundaries around invariants.
Evolving an Existing System Toward DDD
- Draw a Context Map of what exists. Identify the implicit Bounded Contexts, even if they were never designed that way.
- Identify the Core Domain. What part of the system provides the most business value? Focus investment there.
- Introduce an Anticorruption Layer between the new model and legacy code. Don’t try to fix everything at once.
- Refactor toward explicit concepts. Extract implicit business rules into Specifications, Policies, and domain Services.
- Cultivate the language. As the team refines the model, rename classes, methods, and modules to match.
Making Aggregate Design Decisions
- Start with the true invariants — the rules that must never be violated within a single transaction.
- Prefer small Aggregates (even single-Entity Aggregates) unless invariants demand grouping.
- Reference other Aggregates by identity (store the ID, not the object reference) to keep boundaries clean.
- Use eventual consistency for rules that span Aggregates — domain events, batch processing, or saga patterns.
Working with Multiple Bounded Contexts
- Always maintain an explicit, up-to-date Context Map.
- Choose integration patterns deliberately based on team relationships and technical needs.
- Test at context boundaries aggressively — these are where misunderstandings manifest.
- Each Bounded Context gets its own Ubiquitous Language. The same term can mean different things in different contexts, and that is fine.
- Translation between contexts should be the responsibility of a clearly defined layer or service.
Recognizing When You Need a Breakthrough
- Requirements keep appearing that don’t fit the model cleanly, requiring increasingly complex workarounds
- Subtle bugs resist fixing because the model doesn’t match how the business actually works
- Domain experts say the model is “too technical” or don’t recognize their concepts in it
- The team is spending more time on special cases and exceptions than on core functionality
- A simpler model “almost” works but needs constant patching
When a breakthrough presents itself: Evaluate honestly. The short-term cost of a major refactoring is real, but the long-term cost of not making the shift is usually higher. The deeper model will simplify what seemed complex and open possibilities that were invisible before.