Overview
Domain-Driven Design (DDD), introduced by Eric Evans, is an approach to building software around a deep model of the business domain, developed together with domain experts using a shared ubiquitous language. It has two parts: strategic design (splitting a large domain into bounded contexts and mapping their relationships) and tactical design (patterns like entities, value objects, aggregates, domain events, repositories).
Bounded contexts are the most important idea for system design: each context has its own model and language (a 'Product' means different things to Catalog and Shipping), and they make natural boundaries for modules or microservices. Aggregates define consistency boundaries: invariants are enforced within one aggregate in one transaction, while cross-aggregate consistency is eventual.
To sales, a 'customer' is a lead with a pipeline stage; to billing, it is an account with invoices; to support, a person with tickets. DDD lets each department keep its own precise model instead of forcing one bloated 'Customer' on everyone.
When to use it
- Complex business domains (insurance, logistics, finance, healthcare).
- Finding service or module boundaries.
- Teams struggling with inconsistent terminology and tangled models.
- Designing consistency and transaction boundaries.
Where it shows up in interviews
Recognize it when: how do you split this domain?
- Design an insurance platform
- Design a logistics system
- Decompose an e-commerce monolith
Recognize it when: what must be updated atomically?
- Design a booking system
- Design order and inventory models
Where it is used in real software
A workshop technique by Alberto Brandolini where teams map domain events on sticky notes to discover contexts and aggregates.
Many companies (Zalando, ING, Uber's domain-oriented architecture) align services with bounded contexts.
Frameworks combining DDD aggregates with CQRS and event sourcing.
Key terms
- Ubiquitous language
- Shared terms used by developers and domain experts, in code too.
- Bounded context
- Boundary within which a model and language apply consistently.
- Aggregate
- Cluster of objects treated as one unit for changes, with a root.
- Value object
- Immutable, identity-less object defined by its values (Money).
- Domain event
- Something important that happened (OrderPlaced).
- Context map
- Relationships between contexts (upstream/downstream, anti-corruption layer).
How it works, step by step
- 1Explore the domain with experts
Event storming to find events, commands, and actors.
- 2Identify bounded contexts
Where language and models change.
- 3Map context relationships
Who depends on whom; add anti-corruption layers.
- 4Design aggregates
Small, enforcing invariants in one transaction.
- 5Integrate with domain events
Cross-aggregate and cross-context consistency is eventual.
Bounded contexts in e-commerce
Same word, different models
| Context | 'Product' means | Key aggregates |
|---|---|---|
| Catalog | Title, description, images, categories | Product |
| Pricing | Price lists, discounts, currency | PriceList, Promotion |
| Inventory | SKU, stock per warehouse | StockItem |
| Ordering | Line item snapshot at purchase | Order (with OrderLines) |
| Shipping | Weight, dimensions, hazmat flags | Shipment |
NOWContext: Catalog | 'Product' means: Title, description, images, categories | Key aggregates: Product
Each context can be a module or service with its own data; they share IDs and events, not one giant Product table.
Implementation
// Value object: immutable, compared by valueexport class Money { private constructor(readonly cents: number, readonly currency: "USD" | "EUR") {} static of(cents: number, currency: "USD" | "EUR") { if (!Number.isInteger(cents) || cents < 0) throw new Error("Invalid amount"); return new Money(cents, currency); } add(other: Money) { if (other.currency !== this.currency) throw new Error("Currency mismatch"); return Money.of(this.cents + other.cents, this.currency); }} // Aggregate root: all changes go through it, invariants enforced insideexport class Order { private lines: { sku: string; qty: number; price: Money }[] = []; private status: "draft" | "placed" = "draft"; readonly events: { type: string; orderId: string }[] = []; constructor(readonly id: string) {} addLine(sku: string, qty: number, price: Money) { if (this.status !== "draft") throw new Error("Cannot modify a placed order"); if (this.lines.length >= 50) throw new Error("Too many lines"); this.lines.push({ sku, qty, price }); } place() { if (this.lines.length === 0) throw new Error("Empty order"); this.status = "placed"; this.events.push({ type: "OrderPlaced", orderId: this.id }); // other contexts react asynchronously }}Complexity and performance
Keep aggregates small.
Sagas for workflows.
Trade-offs
DDD needs time with domain experts and modeling skill; it pays off in complex domains, not in simple CRUD.
Small aggregates scale and avoid contention but require eventual consistency between them.
Variants and related techniques
Translate a legacy or external model at the boundary.
A small model shared by two contexts, by agreement.
Common mistakes
- One huge aggregate.
Fix: Causes lock contention and big transactions; split by invariants.
- Applying tactical patterns without strategic design.
Fix: Bounded contexts matter more than repositories and factories.
- A single canonical enterprise model.
Fix: Let each context have its own model.
Interview questions
How do bounded contexts help design microservices?
Each bounded context has a consistent model and language and minimal coupling to others, making it a natural candidate for a service with its own data and team.
What is an aggregate and how big should it be?
A cluster of domain objects changed together through a root, enforcing invariants in one transaction. Keep it as small as the invariants allow; reference other aggregates by ID and coordinate them with domain events.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Identify bounded contexts for a ride-hailing app | Medium | Strategic design. |
| Design aggregates for hotel booking | Hard | Invariants and contention. |