Domain-Driven Design (DDD): Taming Complex Systems with Bounded Contexts and Aggregates

Domain-Driven Design (DDD): Taming Complex Systems with Bounded Contexts and Aggregates
Table of Contents
- The Domain Is Not Your Database
- Strategic DDD: The Big Picture View
- Ubiquitous Language: One Term, One Meaning, Per Context
- Bounded Contexts: The Most Important Concept in DDD
- Context Maps: How Bounded Contexts Relate
- Event Storming: Discovering the Domain Model
- Tactical DDD: Inside the Bounded Context
- Aggregates: Consistency Boundaries
- Entities vs Value Objects
- Domain Events: Cross-Context Communication
- DDD → Microservice Boundary Mapping
- Frequently Asked Questions
- Key Takeaway
The Domain Is Not Your Database
The most common mistake in software design is modeling the code around the database schema rather than the business domain. When a developer asks "What tables do we need?", they're beginning from infrastructure. When a domain expert asks "What happens when a customer places an order?", they're beginning from the domain.
DDD insists: understand the business first, model it in code, then decide how to persist it.
The domain-first model immediately communicates business intent — Order.place(), Order.cancel(), Order.ship(). The database model communicates storage details.
Strategic DDD: The Big Picture View
DDD at the strategic level answers: how do we divide a large, complex domain into manageable pieces, and how do those pieces relate?
Strategic DDD tools:
- Ubiquitous Language: A shared vocabulary between developers and domain experts
- Bounded Contexts: Explicit boundaries around coherent sub-domains
- Context Maps: Diagrams of how bounded contexts interact
Ubiquitous Language: One Term, One Meaning, Per Context
The Ubiquitous Language is a rigorous, shared vocabulary between domain experts and developers — used in conversations, documentation, and code identically:
Practical implementation:
- Build a glossary document with domain experts defining every term
- If a developer introduces a new code term not in the glossary, it must match a corresponding business concept or be added to the glossary
- Rename classes/methods when business terminology changes — the code must stay in sync
Bounded Contexts: The Most Important Concept in DDD
A Bounded Context is an explicit boundary within which a specific domain model applies. The same word means different things in different contexts:
| Concept | Sales Context | Billing Context | Shipping Context |
|---|---|---|---|
| "Customer" | A potential buyer (Lead → Customer) | A billing account with payment methods | A delivery address + contact name |
| "Product" | Something with a price and description | A line item with a SKU and unit price | A physical item with weight and dimensions |
| "Order" | An intent to purchase | A billable invoice with line items | A fulfillment task with a shipping label |
Each context has its own model of "Customer" — they share only a customerId key, not the full object. Context A never directly imports the domain classes from Context B.
Context Maps: How Bounded Contexts Relate
A Context Map documents the relationships between bounded contexts:
| Relationship | Description | Example |
|---|---|---|
| Conformist | Downstream adopts upstream's model completely | Order service adopts Payment service's contract |
| Customer-Supplier | Upstream serves downstream's needs | Billing serves Sales's invoice needs |
| Partnership | Both contexts evolve together with mutual agreement | Order and Inventory maintain joint API |
| Shared Kernel | Two contexts share a subset of the domain model | Shared Money value object |
| Anti-Corruption Layer (ACL) | Translate between incompatible models | Legacy CRM → modern Customer model via ACL |
| Open Host Service | Publish a well-defined API for multiple consumers | Product catalogue serves 10 downstream services |
Event Storming: Discovering the Domain Model
Event Storming (Alberto Brandolini) is a collaborative workshop for discovering a domain model. Developers and domain experts stand at a large whiteboard with colored sticky notes:
Workshop flow:
- Domain experts write all Domain Events (things that happen in the business)
- Developers add Commands that cause each event
- Identify Aggregates that handle each command
- Find Policies (business rules connecting events to commands)
- Mark External System boundaries
After 4-6 hours, the team has a visual model of the entire domain — discovering bounded context boundaries, aggregate boundaries, and cross-context integrations.
Tactical DDD: Inside the Bounded Context
Tactical DDD provides the building blocks for implementing a bounded context.
Aggregates: Consistency Boundaries
An Aggregate is a cluster of domain objects treated as a single unit for data changes. Every change within an aggregate is atomic:
Aggregate design rules:
- Reference other aggregates by ID only — never by object reference
- Enforce all invariants (business rules) within the aggregate boundary
- Keep aggregates small — one that takes 30 seconds to load has too many children
- Never modify two aggregates in one transaction — use Domain Events for cross-aggregate coordination
Entities vs Value Objects
| Aspect | Entity | Value Object |
|---|---|---|
| Identity | Has a unique ID (orderId, userId) | Defined by its attribute values |
| Mutability | Mutable — state changes over time | Immutable — "changed" by replacing |
| Equality | Same ID = same entity | Same values = same value object |
| Examples | Order, Customer, Product | Money, Address, DateRange, EmailAddress |
Domain Events: Cross-Context Communication
When one bounded context needs to react to something that happened in another, use Domain Events — not direct calls to the other context's repositories or services:
Frequently Asked Questions
Is DDD too complex for small teams? Strategic DDD (Ubiquitous Language, identifying bounded contexts) pays off at almost any team size — it improves communication and prevents the most common form of long-term maintainability collapse. Tactical DDD (Aggregates, Value Objects, Domain Events pattern) adds code ceremony that may not be justified for a 3-person team building a simple CRUD app. Apply tactical DDD where the domain is genuinely complex and the business rules are the primary investment.
How many microservices should map to one bounded context? Ideally, one microservice per bounded context. This is the strongest alignment — one team, one bounded context, one deployable service. The anti-pattern is splitting a single bounded context across multiple services (creating a distributed monolith) or merging multiple bounded contexts into one service (recreating a big ball of mud at a service level).
Key Takeaway
DDD is the intellectual foundation that makes complex software stay understandable over time. Bounded Contexts are the answer to "the User object has 200 fields because everyone's feature is adding to it." Aggregates are the answer to "we have inconsistent data because anyone can update any table." Domain Events are the answer to "we can't add a new feature without touching half the codebase." Together, these patterns give a large team the vocabulary and structure to build complex software that remains maintainable for decades.
Read next: Monolith vs. Microservices in 2026 →
Part of the Software Architecture Hub — comprehensive guides from architectural foundations to advanced distributed systems patterns.
