Domain-Driven Design
Domain-driven design (DDD) is an approach to software development that centers the design and architecture around the core business domain, using a shared language between engineers and domain experts to model complex systems.
What It Really Means
Most software problems are not technology problems — they are understanding problems. The code does not match how the business actually works. Engineers use technical terms ("entities," "controllers," "repositories") while business people use domain terms ("policy," "claim," "underwriting"). DDD bridges this gap.
The central idea is ubiquitous language: a shared vocabulary used by both developers and domain experts in code, documentation, and conversation. If the insurance business says "policy," the code has a Policy class, not an InsuranceProduct or Contract. If the business says "bind a policy," the code has policy.bind(), not policy.activate() or policy.setStatus('active'). The code reads like the domain.
DDD was introduced by Eric Evans in his 2003 book. It provides both strategic patterns (how to decompose large systems into bounded contexts) and tactical patterns (how to model domain logic within a context). The strategic patterns are particularly relevant for microservices architecture, where bounded contexts map naturally to service boundaries.
DDD is not for every project. It is most valuable for complex domains where the business logic is the hard part — insurance, healthcare, finance, logistics. For simple CRUD applications, DDD adds unnecessary ceremony.
How It Works in Practice
Strategic Patterns
Bounded Context: A boundary within which a specific domain model is defined and consistent. In an e-commerce system, "Product" means different things in different contexts:
- Catalog context: Product has a name, description, images, and categories
- Inventory context: Product has SKU, quantity, warehouse location
- Pricing context: Product has base price, discount rules, tax category
Each bounded context has its own model of Product, optimized for its specific purpose. Trying to create one universal Product model that serves all contexts leads to a bloated, confusing entity.
Context Mapping: How bounded contexts interact. Common relationships:
- Customer/Supplier: One context provides data that another consumes
- Shared Kernel: Two contexts share a small subset of the domain model
- Anti-Corruption Layer: A translation layer that protects one context from another's model
- Published Language: A well-defined, versioned interface between contexts
Tactical Patterns
Entity: An object defined by its identity, not its attributes. Two customers with the same name are different customers because they have different IDs.
Value Object: An object defined by its attributes, not its identity. Two Money(100, "USD") objects are equal regardless of where they came from. Value objects are immutable.
Aggregate: A cluster of entities and value objects treated as a single unit for data consistency. An Order aggregate contains OrderLines, ShippingAddress, and PaymentInfo. All changes go through the aggregate root (Order), which enforces business invariants.
Domain Event: A record of something that happened in the domain. OrderPlaced, PaymentReceived, ShipmentDispatched. Domain events are the natural bridge to event-driven architecture.
Repository: An abstraction over data access. Retrieves and persists aggregates. The domain layer defines the repository interface; the infrastructure layer implements it (see hexagonal architecture).
Real-World Example: Insurance Domain