Domain-Driven Design (DDD)
DDD is an approach to software design that emphasizes modeling the problem domain accurately and using that domain model as the foundation for architectural decisions.
Two Levels of DDD
Strategic Design (Big Picture)
How to divide the business domain and how parts interact.
| Pattern | Purpose |
|---|---|
| Ubiquitous Language | Shared language between developers and domain experts; reflected in code |
| Bounded Context | Explicit boundary where a model is consistent and valid |
| Subdomain | Business capability area (core, supporting, generic) |
| Context Map | Relationships between bounded contexts |
Tactical Design (Implementation)
Patterns for concretely implementing domain models within each boundary.
| Pattern | Purpose |
|---|---|
| Entity | Object with identity that persists over time |
| Value Object | Immutable object defined by its attributes (no identity) |
| Aggregate | Cluster of entities/values with consistency boundary |
| Repository | Abstraction for persistence (collection-like interface) |
| Domain Service | Stateless behavior that doesn't belong to an entity |
| Domain Event | Something that happened in the domain |
| Factory | Encapsulates complex creation logic |
| Specification | Encapsulates business rules for querying |
Ubiquitous Language
The core practice: Developers and domain experts use the same terms in conversation, code, tests, and documentation.
- If the business says "Policy", the class is
Policy, notPolicyEntityorPolicyDTO - Refactor language as understanding deepens
- Language lives in the bounded context — "Customer" means different things in Sales vs Support
Bounded Contexts
A bounded context defines the scope of a model's applicability.
┌─────────────────────────────────────────────────────┐
│ E-COMMERCE SYSTEM │
├──────────────────┬──────────────────┬───────────────┤
│ SALES │ INVENTORY │ SHIPPING │
│ Context │ Context │ Context │
├──────────────────┼──────────────────┼───────────────┤
│ Product: │ Product: │ Product: │
│ - price │ - SKU │ - weight │
│ - description │ - quantity │ - dimensions │
│ - category │ - warehouse_loc │ - carrier │
└──────────────────┴──────────────────┴───────────────┘
Key rule: A model is only valid within its bounded context. Don't share models across contexts — share data via contracts/anti-corruption layers.
Context Mapping Patterns
| Pattern | Direction | When to Use |
|---|---|---|
| Partnership | ↔ | Two teams succeed/fail together; close coordination |
| Shared Kernel | ↔ | Small shared subset; explicit coordination |
| Customer/Supplier | → | Downstream (customer) drives upstream (supplier) priorities |
| Conformist | → | Downstream has no leverage; adopts upstream model |
| Anticorruption Layer | → | Protect domain from foreign/legacy model; translate |
| Open Host Service | → | Published protocol for many consumers |
| Published Language | ↔ | Well-documented shared language (OpenAPI, Protobuf) |
| Separate Ways | — | No integration needed; contexts truly independent |
Subdomain Types
| Type | Description | Investment |
|---|---|---|
| Core Domain | What makes you money; your competitive advantage | Highest — best people, most rigor, DDD tactical patterns |
| Supporting Subdomain | Necessary but not differentiating | Medium — buy or build simply; don't over-engineer |
| Generic Subdomain | Commodity (auth, billing, email) | Lowest — buy/off-the-shelf; avoid custom code |
Tactical Patterns Deep Dive
Entity
- Has identity that persists across time and state changes
- Equality by identity, not attributes
- Example:
Order,User,Account
Value Object
- No identity — defined entirely by its attributes
- Immutable — replace instead of mutate
- Equality by value (structural equality)
- Example:
Money,Address,Email,DateRange - Prefer value objects — they're simpler, safer, more testable
Aggregate
- Consistency boundary — a cluster of entities/values treated as a unit
- Has a root entity (aggregate root) — only it can be referenced externally
- All external access goes through the root
- Invariants enforced within the aggregate boundary
- One transaction per aggregate (ideally)
Order (Aggregate Root)
├── OrderLine[] (Entities)
├── ShippingAddress (Value Object)
└── BillingAddress (Value Object)
Invariant: Order.total = sum(line.price * line.quantity)
Repository
- Collection-like interface for aggregate persistence
add(aggregate),get(id),remove(aggregate)- Interface in domain, implementation in infrastructure
- One repository per aggregate root (not per entity)
Domain Service
- Stateless operation that doesn't naturally belong to an entity/value
- Named from ubiquitous language:
TransferService,PricingEngine - Not the same as application service (which orchestrates)
Domain Event
- Something that happened that domain experts care about
- Immutable, past tense:
OrderPlaced,InventoryReserved - Published by aggregate; consumed by other aggregates/sagas
- Enables eventual consistency across aggregates/contexts
DDD + Hexagonal Architecture
They're complementary:
┌─────────────────────────────────────┐
│ HEXAGONAL LAYERS │
├─────────────────────────────────────┤
│ Primary Adapters (Driving) │ ← Controllers, CLI, Consumers
├─────────────────────────────────────┤
│ Application Services │ ← Use cases, orchestrate aggregates
├─────────────────────────────────────┤
│ DOMAIN LAYER (Bounded Context) │
│ ├─ Entities │
│ ├─ Value Objects │
│ ├─ Aggregates │
│ ├─ Domain Services │
│ ├─ Domain Events │
│ ├─ Repository Interfaces (Ports) │
│ └─ Domain Event Publishers (Ports) │
├─────────────────────────────────────┤
│ Secondary Adapters (Driven) │ ← Repositories, Event Publishers
├─────────────────────────────────────┤
│ Infrastructure │ ← DB, Message Broker, External APIs
└─────────────────────────────────────┘
Why they fit: DDD wants domain isolated; Hexagonal provides the mechanism (ports/adapters).
Anti-Patterns to Avoid
| Anti-Pattern | Description |
|---|---|
| Anemic Domain Model | Entities are just data bags; logic lives in services |
| God Aggregate | One aggregate spans too much; becomes bottleneck |
| Chatty Aggregates | Aggregates calling each other directly |
| Leaky Contexts | Sharing domain models across bounded contexts |
| Infrastructure Leakage | DB concerns (IDs, annotations) in domain model |
| DDD Lite | Using tactical patterns without strategic design (ubiquitous language, bounded contexts) |
When to Use DDD
| Use DDD When | Don't Use DDD When |
|---|---|
| Complex business logic/rules | Simple CRUD applications |
| Domain evolves frequently | Short-lived prototypes |
| Multiple domain experts involved | Technical complexity only (no domain complexity) |
| Long-term system (5+ years) | Team unfamiliar with DDD (steep learning curve) |
| Need for clear boundaries |
Related Notes
- Architectural Styles — Hexagonal/Clean as DDD enablers
- CQRS Event Sourcing — Advanced patterns for aggregates
- Design Principles — Principles underlying DDD
- Design Patterns — Repository, Factory, Specification as patterns
- Test Driven Development — TDD with aggregates
Sources: Domain-Driven Design (Eric Evans), Advanced Beginner DDD Guide, AWS Prescriptive Guidance, Vaughn Vernon (Implementing DDD), Martin Fowler, Software Engineering: A Modern Approach