Domain-Driven Design is a software development approach where the structure and language of the code directly reflect the business domain it models. Coined by Eric Evans in his 2003 book of the same name, its central premise is that the biggest source of complexity in large software projects is not technical. It is the disconnect between how developers think about the system and how the business actually works.
The core problem DDD solves: in most organisations, developers speak in tables, classes, and endpoints while business experts speak in customers, orders, policies, and workflows. When these vocabularies diverge, requirements get lost in translation, bugs trace back to misunderstandings rather than code errors, and the system gradually drifts from what the business actually needs. DDD bridges that gap by making the business vocabulary the foundation of the code itself.
The Core DDD Concepts – Explained Without Jargon
| Concept | What It Means | Simple Analogy | Why It Matters |
|---|---|---|---|
| Ubiquitous Language | A shared vocabulary used by both developers and business experts in all conversations and code | Using the word ‘Invoice’ in meetings, docs, and class names – never ‘billing_record’ | Eliminates translation errors between business and tech |
| Bounded Context | An explicit boundary within which a domain model applies – outside the boundary, the same word may mean something different | In Sales, ‘Customer’ means a prospect. In Shipping, ‘Customer’ means a delivery recipient. Different contexts, same word. | Prevents models from becoming bloated and contradictory |
| Aggregate | A cluster of related objects treated as a single unit for data changes – with one root object controlling access | An Order Aggregate: the Order is the root; OrderLines, Discounts, ShippingAddress are inside. You change them through the Order. | Enforces consistency rules within a boundary |
| Entity | An object with a unique identity that persists over time, even as its attributes change | A Person: their address changes, their name may change, but their identity (ID) persists | Tracks things that matter as individuals over time |
| Value Object | An object defined entirely by its attributes, with no identity – two identical value objects are interchangeable | A Money amount: $50 USD is $50 USD regardless of which $50 bill represents it | Models concepts that are defined by what they are, not who they are |
| Domain Event | Something significant that happened in the domain – past tense, immutable | ‘OrderPlaced’, ‘PaymentFailed’, ‘CustomerUpgraded’ | Enables loose coupling and event-driven architecture |
| Repository | An abstraction that provides collection-like access to aggregates without exposing persistence details | A library’s catalogue: you request ‘all mystery books from 2020’ without needing to know how they are physically stored | Keeps domain model independent of database choice |
Strategic DDD vs Tactical DDD
DDD has two layers that are often confused. Understanding the distinction prevents applying the right tool in the wrong place.
Strategic DDD – The Big Picture
Strategic DDD is about organising the overall system. It asks: how should we divide this large domain into manageable pieces that can evolve independently? The key tools are Bounded Contexts and Context Maps.
- Bounded Contexts define where a specific model applies – a team owns a context, a codebase represents a context.
- Context Maps show how bounded contexts relate: does the Sales context consume data from the Inventory context? Who depends on whom? Who must conform to whose model?
Strategic DDD is the most valuable part of the approach for large organisations and is applicable even when you are not using any tactical patterns.
Tactical DDD – The Implementation Patterns
Tactical DDD is the set of specific building blocks: Entities, Value Objects, Aggregates, Domain Events, Repositories, and Factories. These are patterns for structuring code inside a bounded context.
A common mistake is applying tactical patterns everywhere without the strategic foundation. Building aggregates and repositories in a 500-line codebase adds complexity without benefit. Tactical DDD earns its place in complex domains with significant business rules.
Context Maps: How Bounded Contexts Relate
When two bounded contexts need to communicate, the relationship between them must be explicitly defined. DDD names these relationship patterns:
| Pattern | What It Means | Example |
|---|---|---|
| Shared Kernel | Two contexts share a subset of the domain model | Billing and Subscription share the ‘Plan’ concept |
| Customer/Supplier | One context (supplier) provides data to another (customer) | Inventory provides stock levels to Order Management |
| Conformist | Downstream context fully adopts upstream’s model with no translation | A new team adopts the legacy system’s data model wholesale |
| Anti-Corruption Layer (ACL) | Downstream context translates upstream’s model to its own language | Your clean Order model wraps a messy legacy ERP system’s data |
| Open Host Service | A context exposes a protocol that others can consume freely | A public API any internal team can call |
| Published Language | A shared, documented format for exchange between contexts | A standard event schema in an event-driven architecture |
When DDD Makes Sense – And When It Does Not
DDD is a good fit when: the domain has complex, evolving business rules that require deep collaboration between developers and domain experts; when multiple teams work on different parts of the same system; when the system will be maintained and extended for years.
DDD is overkill when: the application is primarily CRUD (create, read, update, delete) with little business logic; when the team is small and the codebase is simple; when you are building an MVP and speed matters more than architecture; when there are no domain experts to collaborate with.
The most expensive DDD mistake is applying it to a simple problem because it sounds sophisticated. A straightforward web app with a database does not need aggregates, bounded contexts, or domain events. A large insurance platform with complex policy rules, multiple regulatory contexts, and dozens of teams probably does.
DDD and Microservices: A Natural Fit
Bounded Contexts and microservices align naturally – a bounded context often maps to a microservice boundary. Each service owns its own data, models its own version of shared concepts, and communicates through well-defined interfaces (APIs or domain events).
This alignment solves one of microservices’ hardest problems: how do you decide where to draw service boundaries? The answer DDD provides is: at the boundary where the domain model and ubiquitous language change. When ‘Customer’ means something different in the Shipping service than in the Sales service, that is a signal they should be separate services.
- One bounded context = one microservice (ideal, though not always practical).
- Domain Events become the natural integration mechanism between services – OrderPlaced, PaymentProcessed, ShipmentDispatched.
- Each service can choose its own database technology because it owns its own model – no shared schema coupling.
A Practical Starting Point
If you are introducing DDD to an existing team, start with Ubiquitous Language alone. Before writing code, spend time with domain experts. Learn what they call things. Make your code use those exact names. No renaming ‘invoice’ to ‘billing_document’ because a developer found it cleaner.
This one discipline – using the business vocabulary in code – produces measurable improvements in communication, bug reports, and onboarding speed. Everything else in DDD builds on this foundation. Start there, and only add complexity when the domain genuinely demands it.
