Domain-Driven Design: Modeling the Business, Not the Database
DDD is not about repositories and aggregates first,it is about talking to domain experts until the code speaks their language. Here is how strategic and tactical DDD fit together.
Domain-Driven Design, coined by Eric Evans, has two halves that get confused constantly: strategic DDD, which is about how you divide a large domain into manageable pieces, and tactical DDD, the patterns,entities, value objects, aggregates,you use to model inside one of those pieces. Most teams jump straight to tactical patterns and skip the strategic work that makes them useful.
Strategic DDD: bounded contexts
A bounded context is a boundary within which a specific model and its ubiquitous language apply consistently. The word "Order" means something different in the Sales context (a signed contract) than in the Shipping context (a package to route). Trying to build one unified "Order" model for both is where DDD projects usually go wrong.
- ✓Identify your subdomains: core (your competitive advantage), supporting (necessary but not differentiating), and generic (solved problems,buy, do not build).
- ✓Draw a context map: which contexts exist, and how do they relate?
- ✓Choose relationships deliberately: shared kernel, customer-supplier, or an anticorruption layer when integrating with a context you do not control.
Ubiquitous language
The vocabulary domain experts use in conversation should be the same vocabulary in your code,class names, method names, even variable names. If your code needs a translation layer between "what the business calls it" and "what the code calls it," that gap is where bugs and misunderstandings accumulate.
Tactical DDD: value objects, entities, aggregates
A value object has no identity,it is defined entirely by its attributes and should be immutable. An entity has an identity that persists even when its attributes change.
// Value Object: no identity, defined entirely by its attributes, immutable
final readonly class Money
{
public function __construct(
public int $amount,
public string $currency,
) {}
public function equals(Money $other): bool
{
return $this->amount === $other->amount && $this->currency === $other->currency;
}
}
// Entity: has identity that persists even if attributes change
final class Order
{
public function __construct(
private readonly OrderId $id, // identity
private OrderStatus $status,
private Money $total,
) {}
}Aggregates are consistency boundaries, not object graphs
An aggregate groups entities and value objects that must stay consistent together, and it exposes exactly one entry point: the aggregate root. Keep aggregates small,reference other aggregates by ID, not by object reference, and treat one aggregate per transaction as the default rule.
final class Order
{
private array $lines = [];
private OrderStatus $status;
public function addLine(ProductId $product, int $quantity): void
{
if ($this->status !== OrderStatus::Draft) {
throw new DomainException('Cannot modify a confirmed order.');
}
$this->lines[] = new OrderLine($product, $quantity);
}
public function confirm(): void
{
if (empty($this->lines)) {
throw new DomainException('Cannot confirm an order with no lines.');
}
$this->status = OrderStatus::Confirmed;
}
}The most common DDD mistake is the anemic domain model: classes that are just data bags with getters and setters, while all the business logic lives in a separate "service" layer. Put the invariants inside the aggregate itself,if a rule can be violated by calling a setter, the model is not doing its job.
When DDD is worth the investment
- ✓Business rules are complex and change frequently,not simple CRUD.
- ✓Multiple teams work on different subdomains and need clear ownership boundaries.
- ✓Domain experts are available and willing to collaborate closely with engineering.
DDD's biggest return is not the tactical patterns,it is the shared vocabulary it forces between engineers and the business. Start with the conversations and the bounded contexts. Aggregates and repositories are a means, not the goal.
Need help with this topic? Full Stack Development
Discover this service →