Design approach
Domain-Driven Design
When the business domain is genuinely complex, model the software around it — using the words the experts already use — instead of bending the domain to fit a database schema.
✗ The problem
A big ball of mud, and everyone speaks a different language
Business rules leak into controllers and SQL scripts. The model itself is just data — getters and setters, no behavior. And "cart", "order", "purchase" all mean the same thing to three different people.
// rule buried in a controller, not the domain
class OrderController {
checkout(req) {
if (req.total > 1000) req.discount = 0.1;
db.update('orders', req.id, { total: req.total });
}
}
// the "model" is anemic — just fields
class Order { getTotal(){} setTotal(t){} }
Devs say "cart", Sales says "order", Billing says "invoice" — same concept,
three names, zero shared understanding. Logic ends up wherever it was easiest to paste it.
✓ The approach
↓ guards invariants
Model the domain, speak the experts' language
Code and domain experts share one Ubiquitous Language. Complex domains split into Bounded Contexts; each has its own valid model.
class Order { // Entity — has identity
addLineItem(sku, qty, price) { /* enforces rules */ }
confirm() { /* locks the order */ }
}
class Money { // Value Object — equal by value
constructor(amount, ccy) { ... }
}
Sales
bounded context
Shipping
bounded context
Billing
bounded context
Order
aggregate root
Line item
Line item
✓ See it live
The aggregate root enforces its own rules
The Order aggregate — not a controller — decides what's allowed:
total capped at $1000, and no edits once confirmed.
Order aggregate root
OPEN
Items: —
Total: $0
/ $1000 limit
Accepted / rejected log
✓ Takeaway
Model the domain, not the database
- Ubiquitous Language: code and domain experts use the exact same words.
- Model the domain, not a table — behavior lives on entities, not controllers.
- Aggregates guard invariants: the root is the only door in; it rejects invalid changes.
- Split by Bounded Context: each context gets its own model — no single "god" schema.
- Caution: DDD is heavyweight. Reserve it for complex core domains — plain CRUD doesn't need it (YAGNI).
🎯 Relates: DDD's boundaries drive CQRS & Event Sourcing; aggregates lean on Factories and strong SRP boundaries.
Back to all topics →