Software ArchitectureSystem Design

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

TT
TopicTrick Team
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

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.

text

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:

text

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:

ConceptSales ContextBilling ContextShipping Context
"Customer"A potential buyer (Lead → Customer)A billing account with payment methodsA delivery address + contact name
"Product"Something with a price and descriptionA line item with a SKU and unit priceA physical item with weight and dimensions
"Order"An intent to purchaseA billable invoice with line itemsA fulfillment task with a shipping label
mermaid

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:

RelationshipDescriptionExample
ConformistDownstream adopts upstream's model completelyOrder service adopts Payment service's contract
Customer-SupplierUpstream serves downstream's needsBilling serves Sales's invoice needs
PartnershipBoth contexts evolve together with mutual agreementOrder and Inventory maintain joint API
Shared KernelTwo contexts share a subset of the domain modelShared Money value object
Anti-Corruption Layer (ACL)Translate between incompatible modelsLegacy CRM → modern Customer model via ACL
Open Host ServicePublish a well-defined API for multiple consumersProduct 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:

text

Workshop flow:

  1. Domain experts write all Domain Events (things that happen in the business)
  2. Developers add Commands that cause each event
  3. Identify Aggregates that handle each command
  4. Find Policies (business rules connecting events to commands)
  5. 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:

java

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

AspectEntityValue Object
IdentityHas a unique ID (orderId, userId)Defined by its attribute values
MutabilityMutable — state changes over timeImmutable — "changed" by replacing
EqualitySame ID = same entitySame values = same value object
ExamplesOrder, Customer, ProductMoney, Address, DateRange, EmailAddress
java

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:

java

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.