Skip to main content

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​

TermDefinitionExample
DomainThe business area that software tries to solve"Order" and "inventory management" in an e-commerce site
Domain objectA general term for objects that express the domainTwo kinds: entities and value objects
Domain modelThe overall design made up of domain objects and aggregatesThe overall picture of the structure that expresses the domain
Business ruleA constraint or rule in the domain"Products cannot be added after confirmation"

The two kinds of domain objects​

KindIdentifierBasis for identityExample
EntityYesJudged by identifierOrder, User, Product
Value objectNoJudged by all attribute valuesMoney, 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.

AspectValue objectEntityAggregate
DefinitionA concept expressed by a combination of attributesA concept with a unique identifierA collection of related domain objects
IdentifierNoneYes (ID)The aggregate root has an ID
MutabilityImmutableMutableChangeable via the aggregate root
Identity judgmentAll attribute valuesIdentifier (ID)The aggregate root's ID
LifecycleDepends on the parent objectAn aggregate root exists independently
An entity inside an aggregate follows the aggregate root
Follows the aggregate root's lifecycle
Table representationEmbedded or a value-object tableA dedicated tableMultiple 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​

  1. Access from outside only via the aggregate root
  2. The aggregate root guarantees consistency
  3. 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​

note

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.