Domain-Driven Design (DDD)
Domain-Driven Design is an approach that puts the business domain at the center of software design and builds a shared language between engineers and domain experts. Instead of letting database tables or framework conventions shape the model, DDD shapes the code around how the business actually thinks and talks.
DDD is most valuable when the domain is complex and the cost of misunderstanding is high — billing rules, logistics, healthcare. It's overkill for simple CRUD. The biggest risk is "DDD theater": adopting the patterns (aggregates, value objects) without the domain conversations that give them meaning.
TL;DR
- Use DDD to align code with business concepts.
- Strategic DDD — bounded contexts and a ubiquitous language.
- Tactical DDD — entities, value objects, aggregates, domain events.
- Avoid "DDD theater" — patterns without real domain focus.
Quick Example
A value object that enforces its own validity, plus a domain event:
Strategic DDD
Ubiquitous language
Build a shared vocabulary — "Project," "Member," "Workspace," "Permission" — and use the same terms everywhere: conversations, docs, code, and APIs. When the model and the conversation use different words, bugs and misunderstandings breed in the gap.
Bounded contexts
A bounded context is a boundary within which a model is internally consistent. "Billing" means invoices, subscriptions, and payments; "Identity" means users, sessions, and MFA. Within a context, every name has a precise meaning; across contexts, the same word ("user") can legitimately mean different things. Bounded contexts reduce confusion and define clear ownership boundaries.
Tactical DDD (Building Blocks)
- Entity — an object with identity that persists over time (
User,Project). - Value object — an immutable concept defined by its values (
EmailAddress,Money). - Aggregate — a cluster of domain objects with an aggregate root that enforces invariants at the boundary. Rule of thumb: modify an aggregate only through its root, and keep aggregates small.
- Domain event — a record that "something happened" (
ProjectCreated,MemberInvited); enables decoupling and integration. See Event-Driven Architecture.
DDD and Microservices
Bounded contexts often map cleanly onto microservice boundaries — each service owns one context and its data. But they don't have to: you can apply DDD inside a monolith (a "modular monolith") and capture most of the benefit — clear contexts, a shared language, well-defined aggregates — without distributed-systems overhead.
Best Practices
- Talk to domain experts — the ubiquitous language comes from conversations, not guesses.
- Keep aggregates small and enforce invariants through the root.
- Model value objects for concepts defined by their values — they eliminate a class of bugs.
- Use bounded contexts to scope models and assign ownership.
- Apply DDD where complexity warrants it — not to simple CRUD.
Common Mistakes
Treating the database schema as the domain model
Giant aggregates
FAQ
When should I use DDD — and when is it overkill?
Use DDD when the domain is complex, evolving, and central to the business — where getting the rules subtly wrong is costly (billing, scheduling, compliance). It's overkill for simple CRUD apps, prototypes, or technically-driven systems with little business logic. The strategic parts (ubiquitous language, bounded contexts) are valuable far more often than the full tactical pattern set.
What's the difference between an entity and a value object?
An entity has identity that persists independent of its attributes — two users named "Alice" are different users; a user stays the same entity even as their email changes. A value object has no identity and is defined entirely by its values — two Money(5, "USD") instances are interchangeable, and value objects are immutable. Modeling the right concepts as value objects removes a lot of accidental complexity and bugs.
What is an aggregate and why keep it small?
An aggregate is a cluster of related objects treated as a single unit for data changes, with a root that guards its invariants — you modify the cluster only through that root. Keeping aggregates small limits the transaction/consistency boundary, reduces lock contention, and keeps invariants tractable. Large aggregates create performance problems and tangled rules; coordinate between small aggregates via domain events instead.
Can I do DDD without microservices?
Absolutely. DDD is about modeling, not deployment topology. A modular monolith with clear bounded contexts, a ubiquitous language, and well-designed aggregates captures most of DDD's value with far less operational complexity than microservices. Bounded contexts can later become service boundaries if you split the monolith, but that's an option, not a requirement. See Microservices.
Related Topics
- Clean Architecture — Structuring the domain core
- Design Patterns — Tactical building blocks
- Event-Driven Architecture — Domain events at scale
- Microservices — Contexts as service boundaries
- SOLID Principles — Designing the model