An Introduction to Domain-Driven Design (DDD) Patterns and Practices
This article explains the core concepts, patterns, and architectural styles of Domain-Driven Design, covering model‑driven design, bounded contexts, layered and hexagonal architectures, building blocks such as entities, value objects, aggregates, repositories, factories, and services, with practical examples and diagrams.
Enterprise applications are inherently complex and rely on specialized techniques such as persistence, AJAX, and web services. While developers naturally focus on technical details, a system that does not solve business problems is useless regardless of how well‑engineered its infrastructure is.
Domain‑Driven Design (DDD) —first described by Eric Evans—shifts attention to the core domain, emphasizing the inherent complexity of the business itself. It distinguishes the core domain (business‑specific) from support sub‑domains (generic concerns such as money or time) and concentrates design effort on the core.
DDD provides a set of patterns for building enterprise applications from a domain model. By applying these ideas, developers can construct systems that truly meet business needs.
In this article we will introduce the main DDD patterns, discuss common pitfalls, and highlight tools and resources (especially one) to help you apply DDD in practice.
Code and Model
Using DDD, we aim to create a model of the problem domain. Technical concerns such as persistence, UI, and messaging are added later; they belong to the domain because they affect the system being built. If not, consider buying a packaged product.
When we speak of a model we do not mean a diagram or a set of charts; those are merely views of the model. The model is the set of concepts we choose to implement in code and other software artifacts . In other words, code is the model . Text editors provide a way to work with this model, and modern tools also offer visualizations such as UML class diagrams, ER diagrams, Spring bean docs, Struts/JSF flows, etc.
Figure 1: Model vs Views of the Model
The first DDD pattern is Model‑Driven Design : concepts in the model map to design/code concepts (ideally). Changes in the model imply changes in the code and vice‑versa. DDD does not force object‑oriented implementation; rules engines can also be used, but most mainstream enterprise languages are OO, so models are expressed as classes and interfaces.
Ubiquitous Language
DDD stresses the need for a shared language between domain experts and developers. Instead of describing new user stories by screen fields, experts discuss the underlying attributes or behaviors of domain objects. Developers likewise avoid talking about database tables directly.
If an idea cannot be expressed easily, it signals a missing concept in the domain model, prompting the team to discover and add it.
This idea is not new; XP called it a “language‑system”. It remains valuable for both business and technical stakeholders.
Model and Context
Every model exists within a bounded context (BC) . The context is inferred from the set of end‑users interacting with the system (e.g., a front‑office trading system or a POS system). Terms meaningful in one BC may be meaningless elsewhere.
When multiple BCs interact—exchanging files, messages, or API calls—we must consider translation between concepts across BCs.
DDD defines several levels of collaboration between BCs:
Published Language : BCs agree on a common language (e.g., a set of XML schemas on an ESB) for interaction.
Open Host Service : A BC exposes services (e.g., RESTful APIs) that others can consume.
Shared Kernel : Two BCs share a common code library as a lingua franca.
Consumer/Provider : One BC uses another’s service and is a stakeholder, influencing the provider.
Conformist : One BC consumes another’s service without being a stakeholder, simply conforming to its contract.
Anti‑Corruption Layer : An adapter layer isolates a BC from changes in another BC.
Figure 2: Bounded Context Relationship Spectrum
Strategic DDD involves mapping BCs and their dependencies to understand integration risks and opportunities.
Figure 3: Context Mapping Example
Layered and Hexagonal Architecture
DDD cares primarily about the domain layer but also acknowledges the presence of presentation, application, and infrastructure (persistence) layers. This yields the classic layered architecture (Figure 4).
Figure 4: Layered Architecture
Historically many multi‑layer systems have been built, but DDD warns against “anemic” domain models where business logic leaks into the application or presentation layers.
The application layer should contain only transaction management, security, and orchestration logic, delegating domain behavior to the domain layer. The presentation layer serializes/deserializes DTOs and forwards user actions to application services.
Layered architecture can lead to a linear dependency stack from presentation down to infrastructure, which may be undesirable for testing. Tools like FitNesse can exercise the system from the user’s perspective by bypassing the presentation layer and invoking application services directly.
Multiple persistence implementations (e.g., RDBMS for production, in‑memory for tests) can be supported via repository abstractions.
Hexagonal (Ports & Adapters) Architecture
An alternative view treats the system as a hexagon (Figure 5). External users interact through primary ports (e.g., UI, FitNesse), while other BCs interact via secondary ports (e.g., REST, ESB). Infrastructure adapters implement these ports, keeping the domain core isolated.
Figure 5: Hexagonal Architecture
Building Blocks
In OO languages, DDD’s building blocks are familiar: entities , value objects , and modules (packages) . For example, a Java @Entity maps to a DDD entity, while a value object might encapsulate money with currency and rounding rules.
Value objects should be immutable and provide side‑effect‑free operations. Example in Java:
Money m1 = new Money("GBP", 10);
Money m2 = new Money("GBP", 20);
Money m3 = m1.add(m2);Adding m2 to m1 does not modify m1 ; it returns a new Money instance representing the sum.
Value objects also implement proper equals() and hashCode() methods, making them suitable as map keys (e.g., SocialSecurityNumber, RegistrationNumber, URL).
Entities are typically mutable, persisted, and have a lifecycle. Modules (packages or namespaces) enforce decoupling and prevent a “big mudball” of code.
Aggregates and Aggregate Roots
An aggregate groups related entities and value objects under a single aggregate root . Only the root is referenced from outside the aggregate, ensuring internal consistency. For example, an Order (root) contains OrderItem entities.
The aggregate root enforces invariants (e.g., once an order is shipped, its items cannot be modified). Cross‑aggregate invariants may require higher‑level coordination or isolation levels.
Repositories, Factories, and Services
Repositories abstract persistence, returning aggregate roots that satisfy criteria (e.g., CustomerRepository.findByLastName(String) ). Implementations may vary (Hibernate, in‑memory) while the domain layer depends only on the repository interface.
Factories create new aggregate instances. For example, a Customer might invoke placeOrder() to create an Order . To avoid circular dependencies, the Dependency Inversion Principle can be applied: introduce an OrderOwner interface that Customer implements, or use an OrderFactory provided by the order module.
Figure 6: Customer and Order (Circular Dependency)
Figure 7: Customer depends on Order via OrderOwner interface
Figure 8: Order depends on Customer via OrderFactory
Domain services encapsulate business logic that does not naturally belong to an entity (e.g., money transfer, integration with a ledger system). Implementations may live in the infrastructure layer.
Application services sit above the domain layer, orchestrating use cases: they deserialize requests, retrieve aggregates via repositories, invoke domain operations, and serialize responses. They may also call infrastructure services such as PDF generation or email sending.
In summary, DDD provides a rich set of patterns—model‑driven design, ubiquitous language, bounded contexts, layered/hexagonal architectures, aggregates, repositories, factories, and services—that help engineers build systems aligned with business requirements rather than merely technical convenience.
For deeper coverage, see Eric Evans’ 500‑page book; the remainder of this article will highlight areas where practitioners often struggle to apply DDD effectively.
Architects Research Society
A daily treasure trove for architects, expanding your view and depth. We share enterprise, business, application, data, technology, and security architecture, discuss frameworks, planning, governance, standards, and implementation, and explore emerging styles such as microservices, event‑driven, micro‑frontend, big data, data warehousing, IoT, and AI architecture.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.