Hi everyone, and welcome to the first post!
Before we can explore how AI might help with Domain-Driven Design, we need a shared map of what DDD actually contains. So let's build a reference list of the strategic and tactical patterns together. Below is a starting point drawn mostly from Eric Evans' Domain-Driven Design (the Blue Book) and Vaughn Vernon's Implementing Domain-Driven Design (the Red Book). Please correct, add, and challenge anything. Once we've refined it, I'll turn it into a wiki page for the community.
Strategic patterns
These deal with the big picture: how to divide a large domain, how models and teams relate, and where to focus effort.
Foundations
- Domain: The sphere of knowledge and activity the software is built to serve, such as insurance underwriting, freight logistics, or online retail. DDD's central argument is that the real complexity of most software lies in the domain itself rather than the technology, so a well-understood model of the domain should drive the design.
- Subdomains: A large domain almost always breaks down into smaller areas of expertise. The Core subdomain is where the business differentiates itself and deserves the best people and most design effort. Supporting subdomains are specific to the business but don't provide a competitive edge, so they can be built more simply. Generic subdomains are problems many businesses share, like identity management or invoicing, and are usually best handled with off-the-shelf solutions.
- Ubiquitous Language: A rigorous vocabulary developed jointly by developers and domain experts and used everywhere: conversations, user stories, diagrams, tests, and code. If the code has a class called
Policy, domain experts should recognize the term and mean the same thing by it. When the language feels awkward or ambiguous, that's a signal the model needs refining. The language is only valid within a single bounded context. - Bounded Context: An explicit boundary, often aligned with a team, codebase, or service, within which a particular model is defined and kept consistent. The same word can legitimately mean different things in different contexts; a "Customer" in Sales might be a lead with contact preferences, while in Billing it's an account with payment terms. Instead of forcing one unified model across the whole system, DDD lets each context own its model and makes the boundaries explicit.
- Context Map: A diagram or document showing every bounded context in a system, who owns it, and how the contexts relate and exchange information. It describes the current reality rather than the ideal, which means it exposes integration points, team dynamics, and risks. It's often the first strategic artifact a team produces when working with an existing system.
Context mapping relationships
These describe how two bounded contexts relate. An upstream context influences a downstream one, not the other way around.
- Partnership: Two teams whose contexts depend on each other so heavily that they succeed or fail together. They plan jointly, coordinate releases, and manage the integration as a shared responsibility. It works well with close communication but becomes costly as coordination overhead grows.
- Shared Kernel: Two teams agree to share a small, explicitly defined subset of the model, along with its code or schema. Neither team can change the kernel without consulting the other. It reduces duplication but creates coupling, so the kernel should be kept as small as possible.
- Customer–Supplier: An upstream (supplier) team provides something a downstream (customer) team depends on, and the downstream team's needs are factored into the upstream team's planning. Negotiated interfaces and shared acceptance tests help keep the relationship healthy. It only works if the upstream team is genuinely motivated to serve the downstream.
- Conformist: The downstream team adopts the upstream model wholesale, with no translation, because the upstream has little reason to accommodate them (a large external vendor, for example). This keeps integration simple, but it means the downstream model is shaped by someone else's decisions.
- Anticorruption Layer: A translation layer the downstream team builds to keep an unwanted upstream model from leaking into its own. It uses façades, adapters, and translators to convert between the two models. It's especially valuable when integrating with legacy systems or poorly designed external APIs.
- Open Host Service: The upstream context exposes a well-defined protocol or API designed for many consumers, rather than building custom integrations for each one. New needs are met by extending the protocol. It's often paired with a Published Language.
- Published Language: A well-documented, shared format for exchanging information between contexts, such as a public JSON schema or an industry standard like HL7 in healthcare. It acts as a neutral medium, so neither side needs to understand the other's internal model.
- Separate Ways: When the cost of integrating outweighs the benefit, contexts simply don't integrate and each solves its needs independently. Some duplication may result, but this is a legitimate design choice rather than a failure.
- Big Ball of Mud: A part of the system where models are tangled and boundaries are unclear or absent. Rather than applying sophisticated modeling inside it, you draw a boundary around it on the context map and prevent the mess from spreading, often with an anticorruption layer.
Distillation
Distillation is the process of separating the core domain from everything else so that effort goes where it matters most.
- Domain Vision Statement: A short document, about a page, written early on that describes the core domain and the value it will bring. It deliberately leaves out anything that doesn't distinguish the business. It gives the team a shared direction and helps decide what belongs in the core.
- Highlighted Core: Techniques for making the core domain easy to spot, such as a brief distillation document describing the core elements and how they interact, or flagging core elements directly in the codebase. This helps newcomers and domain experts quickly see what matters most.
- Cohesive Mechanisms: Complex algorithmic or computational parts that aren't themselves the domain, like graph traversal or constraint solving, get extracted into separate lightweight frameworks. The core model then expresses what it needs, while the mechanism handles how. This keeps the core focused on business concepts.
- Segregated Core: Refactoring the model to physically separate core concepts from supporting ones, increasing the core's cohesion and reducing its coupling to other code. It's useful when the core is buried inside a large model, though it comes with a real refactoring cost.
- Abstract Core: When even the core is large, you identify its most fundamental concepts and express them as abstract classes or interfaces in their own module, with specialized implementations elsewhere. This gives a concise overview of the core that's easier to understand and communicate.
Large-scale structure
These patterns give a large system a coherent overall shape so that people can understand their part in the whole.
- Evolving Order: A large-scale structure should emerge and evolve alongside the application rather than being imposed up front. A structure applied too early can constrain good modeling. The team should be willing to change or abandon a structure as understanding grows.
- System Metaphor: A concrete analogy that captures the essence of the system and gives everyone an intuitive way to reason about it, a concept borrowed from Extreme Programming. A good metaphor aligns thinking across the team, but it should be dropped if it starts to mislead.
- Responsibility Layers: Organizing the domain model into broad layers according to responsibility and rate of change, such as layers for capabilities, operations, and decision support. Higher layers can depend on lower ones, but not the reverse. This makes the model's overall shape easier to grasp.
- Knowledge Level: A group of objects that describes and constrains how another group of objects behaves. For example, a set of employee types whose rules determine how individual employees are paid. It allows behavior to be reconfigured without code changes, at the cost of added abstraction.
- Pluggable Component Framework: In a mature model, an abstract core of interfaces and interactions can serve as a framework into which different implementations are plugged, as long as they conform to the contracts. It enables independent development of components but requires a deep, stable understanding of the domain and is hard to get right early.
Tactical patterns
These are the building blocks for expressing a model in code within a single bounded context.
Building blocks
- Entity: An object defined primarily by its identity and continuity over time rather than by its attributes. A
Customeris still the same customer after changing their name and address. Entities need a clear identity strategy and tend to focus on lifecycle and behavior. - Value Object: An object that describes a characteristic of something and has no identity of its own, so two value objects with the same attributes are interchangeable. Examples include
Money,Address, andDateRange. They should be immutable, which makes them safe to share and easy to reason about, and they're a natural home for validation and related behavior likeMoney.add(). DDD encourages favoring value objects over entities wherever possible. - Aggregate: A cluster of entities and value objects treated as a single unit for data changes, with one entity acting as the aggregate root. Outside objects may only reference the root, and all changes go through it, which lets it enforce the invariants (business rules that must always hold true). Vernon's widely cited guidelines are to keep aggregates small, reference other aggregates by ID only, and modify just one aggregate per transaction, using eventual consistency between them.
- Domain Event: A record of something that happened in the domain that domain experts care about, named in the past tense, like
OrderPlacedorPaymentReceived. Events are immutable and carry the relevant data. They're used to trigger side effects, communicate between aggregates and bounded contexts, and achieve eventual consistency. They weren't among the original Blue Book building blocks but were added later and are now central to modern DDD. - Domain Service: A stateless operation representing a significant domain process that doesn't naturally belong to any entity or value object, often because it involves several aggregates, such as transferring funds between accounts. It's named using the ubiquitous language. Overusing domain services leads to an anemic domain model, where entities become mere data holders.
- Repository: Provides the illusion of an in-memory collection of aggregates, with methods to add, find, and remove them, while hiding the underlying persistence mechanism. There's typically one repository per aggregate root, not per table or entity. The interface belongs to the domain layer, while the implementation lives in infrastructure.
- Factory: Encapsulates the logic for creating complex objects or aggregates so they always start life in a valid state with invariants satisfied. A factory can be a method on an aggregate root, a standalone class, or a builder. It keeps construction complexity out of both the client and the object itself.
- Module: Groups related concepts, such as packages or namespaces, with high cohesion inside and low coupling between them. Module names should come from the ubiquitous language and tell a story about the domain. Grouping by technical type (folders called
modelsorservices) is discouraged in favor of grouping by domain concept. - Layered Architecture: Separates the system into layers, typically presentation, application, domain, and infrastructure, so that the domain layer is isolated from technical concerns and can evolve freely. Modern alternatives like Hexagonal and Onion architecture go further by inverting dependencies so that the domain depends on nothing else.
- Application Service: The layer between the outside world and the domain, where each method usually corresponds to a use case. It loads aggregates from repositories, invokes domain behavior, and handles transactions, security, and event publishing. It contains no business rules; if it starts making business decisions, logic is leaking out of the domain.
- Specification: Encapsulates a business rule as an object with a method like
isSatisfiedBy(candidate). Specifications can be combined with and, or, and not. They serve three purposes: validating an object, selecting objects from a collection or query, and describing what a newly created object must satisfy. They keep business rules explicit, named, and reusable.
Supple design
These patterns make a model easier to understand, use, and change safely.
- Intention-Revealing Interfaces: Classes and operations are named to describe their effect and purpose rather than how they work. A developer should understand what
paint.mixIn(otherPaint)does without reading its implementation. Names should come straight from the ubiquitous language. - Side-Effect-Free Functions: Put as much logic as possible into operations that return results without changing any observable state. Such functions are easier to test, combine, and reason about. Value objects are a natural home for them, while state-changing commands are kept separate and simple.
- Assertions: Make the post-conditions of operations and the invariants of classes and aggregates explicit, through tests, contracts, or documentation. This lets developers understand the effects of an operation without tracing through its implementation.
- Conceptual Contours: Break design elements apart along the natural seams of the domain. When changes tend to land in well-contained areas, the contours probably fit; when a simple change forces edits in many places, they don't. The model is reshaped over time to match these seams.
- Standalone Classes: Minimize dependencies so that a class can be understood on its own. Every dependency adds to the mental load of understanding a piece of code. Low coupling here is a deliberate modeling goal, not just a technical nicety.
- Closure of Operations: Where it fits, define operations whose return type is the same as the type of their arguments, such as adding two
Moneyvalues to getMoney, or combining two specifications into a new specification. Such operations compose naturally without introducing new concepts. - Declarative Design: Express rules and behavior as statements of what should happen rather than step-by-step instructions of how, for example by combining specifications or using a small domain-specific language. This makes the model read more like the domain and reduces procedural noise.
Commonly paired practices (not strictly DDD, but often used alongside it)
- Event Storming: A collaborative workshop format created by Alberto Brandolini, in which domain experts and developers map a business process using sticky notes on a large wall. It starts with domain events in timeline order, then adds commands, actors, policies, aggregates, and hot spots. It's one of the fastest ways to surface bounded contexts and gaps in understanding.
- Domain Storytelling: A technique developed by Stefan Hofer and Henning Schwentner in which domain experts tell concrete stories about their work, recorded as simple pictographic diagrams of actors, work objects, and activities. It's excellent for learning a new domain and discovering where boundaries lie.
- CQRS (Command Query Responsibility Segregation): Separates the model used to change state from the model used to read it. The write side can use rich aggregates while the read side uses simple, denormalized views optimized for queries. It adds complexity, so it's best applied selectively rather than across a whole system.
- Event Sourcing: Instead of storing an aggregate's current state, you store the full sequence of domain events that produced it, and rebuild state by replaying them. It provides a complete audit trail and the ability to answer questions about the past, but introduces challenges around event versioning and building read models.
- Hexagonal Architecture (Ports and Adapters): An architecture described by Alistair Cockburn that places domain and application logic at the center, with ports (interfaces) defining how it talks to the outside world and adapters implementing those ports for specific technologies like databases or message queues. It keeps the domain free of infrastructure dependencies. Onion and Clean Architecture are close relatives.
- Sagas and Process Managers: Coordinate long-running business processes that span multiple aggregates or bounded contexts by reacting to events and issuing commands. Since there's no single transaction covering every step, they track progress and trigger compensating actions when a step fails.
Looking forward to building this together!
Source: r/DDDWithAI · by /u/codeconjecture