The Basic Concepts of DDD — Domain Objects, Aggregates, Repositories
Each sticky note you put on the wall in the previous chapter maps to one of the terms covered here. This chapter moves into tactical design and defines them. The ubiquitous language and the bounded context are covered in Chapter 5.
What is domain-driven design?
Domain-Driven Design (DDD) is a software design approach centered on business logic.
DDD starts design from the "business problem," whereas the traditional approach starts from "technical constraints."
Definitions of terms
Let's define the important terms for learning DDD.
Basic terms
| Term | Definition | Example |
|---|---|---|
| Domain | The business area that software tries to solve | "Order" and "inventory management" in an e-commerce site |
| Domain object | A general term for objects that express the domain | Two kinds: entities and value objects |
| Domain model | The overall design made up of domain objects and aggregates | The overall picture of the structure that expresses the domain |
| Business rule | A constraint or rule in the domain | "Products cannot be added after confirmation" |
The two kinds of domain objects
| Kind | Identifier | Basis for identity | Example |
|---|---|---|---|
| Entity | Yes | Judged by identifier | Order, User, Product |
| Value object | No | Judged by all attribute values | Money, Address, Email |
Comparing value objects, entities, and aggregates
An aggregate is not a kind of domain object but a concept of boundaries; we still line it up in the same table because contrasting it makes the difference in roles easier to grasp.
| Aspect | Value object | Entity | Aggregate |
|---|---|---|---|
| Definition | A concept expressed by a combination of attributes | A concept with a unique identifier | A collection of related domain objects |
| Identifier | None | Yes (ID) | The aggregate root has an ID |
| Mutability | Immutable | Mutable | Changeable via the aggregate root |
| Identity judgment | All attribute values | Identifier (ID) | The aggregate root's ID |
| Lifecycle | Depends on the parent object | An aggregate root exists independently An entity inside an aggregate follows the aggregate root | Follows the aggregate root's lifecycle |
| Table representation | Embedded or a value-object table | A dedicated table | Multiple tables (within the aggregate boundary) |
Understanding with concrete examples
// Value object: money
class Money
{
private int $amount;
private string $currency;
public function __construct(int $amount, string $currency)
{
$this->amount = $amount;
$this->currency = $currency;
}
// Immutable: the result of a calculation returns a new instance
public function add(Money $other): Money
{
return new Money($this->amount + $other->amount, $this->currency);
}
// Identity: judged by all attribute values
public function equals(Money $other): bool
{
return $this->amount === $other->amount
&& $this->currency === $other->currency;
}
}
// Entity: an order
// It is also the aggregate root of the Order aggregate (it groups Order and OrderLine)
class Order
{
private OrderId $id; // Identifier
private OrderStatus $status;
private Money $total;
private array $orderLines; // Entities within the aggregate
// Mutable: the state changes
public function confirm(): void
{
$this->status = OrderStatus::CONFIRMED;
}
// Identity: judged by ID
public function equals(Order $other): bool
{
return $this->id->equals($other->id);
}
// Accessed only via the aggregate root
public function addItem(ProductId $productId, int $quantity): void
{
$this->orderLines[] = new OrderLine($productId, $quantity);
}
}
Note that this addItem() is a simplified version meant to convey the concept. Giving each line an identifier and a unit price is covered in Chapter 9, "Entities".
Criteria for choosing between them
Examples of judgment:
"Money"
Is an identifier needed? → NO
→ Value object
"Order"
Is an identifier needed? → YES
Exists independently? → YES
A relation to other objects? → YES (related to order lines)
→ The aggregate root of the Order aggregate (an order and its order lines are managed together)
"Order line"
Is an identifier needed? → YES
Exists independently? → NO (depends on the order)
→ An entity within the Order aggregate
Aggregate
An aggregate is not a domain object; it is a structure (boundary) that groups domain objects.
Rules of an aggregate
- Access from outside only via the aggregate root
- The aggregate root guarantees consistency
- Update one aggregate per transaction is recommended (handle updates across multiple aggregates with eventual consistency)
// Good: access via the aggregate root (Order)
$order = $orderRepository->findById($orderId);
$order->addItem($productId, $quantity);
// Bad: accessing OrderLine directly
$orderLine = $orderLineRepository->findById($lineId); // This is bad
Repository
A repository is an object responsible for persisting and restoring an aggregate.
From the next chapter, "Value objects", we look at each concept in detail.
Further reading
If you want to learn more about the basic concepts of DDD, see the following resources.
Books
- Eric Evans, "Domain-Driven Design: Tackling Complexity in the Heart of Software" (2003) - the original text of DDD, with detailed explanations of every concept
- Vaughn Vernon, "Implementing Domain-Driven Design" - a practical DDD book rich in implementation examples for value objects and entities
Online resources
- DDD Reference (Eric Evans' free PDF) - a reference that summarizes key concepts such as the ubiquitous language, the bounded context, and aggregates
- Martin Fowler's blog - concise explanations of each DDD concept
The concepts of DDD may feel difficult at first, but if you write code along with the implementation examples in the following chapters, you get used to them gradually.