Domain-Driven Design
Design software systems that model complex business domains. Covers bounded contexts, aggregates, value objects, domain events, context mapping, and the patterns that align code structure with business understanding.
Domain-Driven Design (DDD) is a set of principles for building software that accurately models complex business domains. Instead of starting with a database schema or API endpoints, DDD starts with understanding the business domain and encoding that understanding directly into the code.
Strategic Design
Bounded Context:
A linguistic boundary where a term has a specific meaning
Example: "Account" means different things in different contexts
Banking Context: Account = financial account (balance, transactions)
Identity Context: Account = user account (email, password, profile)
Marketing Context: Account = customer account (segments, campaigns)
Each bounded context has its own model, its own code, its own database
They communicate through well-defined interfaces
Context Mapping
┌──────────────┐
│ Banking │
│ Context │
│ │
│ Account = │
│ {balance, │
│ txns} │
└──────┬───────┘
│ ACL (Anti-Corruption Layer)
│ Translates between models
┌──────┴───────┐
│ Identity │
│ Context │
│ │
│ Account = │
│ {email, │
│ password} │
└──────────────┘
Relationship Patterns:
Shared Kernel: Two contexts share a subset of model
Customer-Supplier: Upstream serves downstream's needs
Conformist: Downstream conforms to upstream's model
ACL: Downstream translates upstream's model
Published Language: Both agree on a shared interchange format
Separate Ways: No integration (duplicate data)
Tactical Patterns
Entities
class Order:
"""Entity: identified by ID, has lifecycle."""
def __init__(self, order_id: OrderId, customer_id: CustomerId):
self.id = order_id
self.customer_id = customer_id
self.items: list[OrderItem] = []
self.status = OrderStatus.DRAFT
self._events: list[DomainEvent] = []
def add_item(self, product: Product, quantity: int):
if self.status != OrderStatus.DRAFT:
raise OrderError("Cannot add items to non-draft order")
item = OrderItem(product.id, product.price, quantity)
self.items.append(item)
self._events.append(ItemAdded(self.id, item))
def submit(self):
if not self.items:
raise OrderError("Cannot submit empty order")
self.status = OrderStatus.SUBMITTED
self._events.append(OrderSubmitted(self.id, self.total))
@property
def total(self) -> Money:
return sum(item.subtotal for item in self.items)
Value Objects
@dataclass(frozen=True) # Immutable
class Money:
"""Value Object: identified by value, not by ID."""
amount: Decimal
currency: str
def __add__(self, other: "Money") -> "Money":
if self.currency != other.currency:
raise ValueError("Cannot add different currencies")
return Money(self.amount + other.amount, self.currency)
def __mul__(self, quantity: int) -> "Money":
return Money(self.amount * quantity, self.currency)
@dataclass(frozen=True)
class Address:
street: str
city: str
state: str
zip_code: str
country: str
Domain Events
@dataclass(frozen=True)
class OrderSubmitted:
"""Domain Event: something that happened in the domain."""
order_id: str
total: Money
occurred_at: datetime = field(default_factory=datetime.utcnow)
class OrderEventHandler:
def handle(self, event: OrderSubmitted):
# Trigger side effects in other bounded contexts
self.inventory_service.reserve_items(event.order_id)
self.notification_service.send_confirmation(event.order_id)
self.analytics_service.record_order(event.order_id, event.total)
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| Anemic domain model | Business logic in services, entities are data bags | Rich domain model with behavior |
| Shared database across contexts | Tight coupling, schema conflicts | Database per bounded context |
| God entity (mega-aggregate) | Performance, concurrency issues | Small aggregates, consistency boundaries |
| No ubiquitous language | Developers and business speak different languages | Shared vocabulary in code AND conversation |
| DDD everywhere | Over-engineering simple domains | DDD for complex cores, CRUD for simple subdomains |
DDD is not about patterns — it is about deeply understanding the business domain and encoding that understanding into code that evolves with the business.