The Basic Concepts of DDD — Domain Objects, Aggregates, Repositories, Ubiquitous Language
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 |
A more detailed comparison of domain objects
Let's look more closely at the differences between value objects, entities, and aggregates.
| 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 | Exists independently | 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
class Order
{
private OrderId $id; // Identifier
private OrderStatus $status;
private Money $total;
// 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);
}
}
// Aggregate: the Order aggregate (groups Order and OrderLine)
class Order // Aggregate root
{
private OrderId $id;
private array $orderLines; // Entities within the aggregate
// Accessed only via the aggregate root
public function addItem(ProductId $productId, int $quantity): void
{
$line = new OrderLine($this->id, $productId, $quantity);
$this->orderLines[] = $line;
}
}
Criteria for choosing between them
Examples of judgment:
"Money"
Is an identifier needed? → NO
→ Value object
"Order"
Is an identifier needed? → YES
A relation to other objects? → YES (related to order lines)
Consistency boundary? → An order and its order lines are managed together
→ The aggregate root of the Order aggregate
"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.
The core ideas of DDD
The ubiquitous language
The ubiquitous language is the language used in common across the entire team. When everyone involved in the project—developers, domain experts (business stakeholders), product managers, and so on—communicates using the same words, you prevent misunderstandings.
Why the ubiquitous language matters
In the traditional approach, business stakeholders and developers use different words, which causes the following problems.
❌ Without a ubiquitous language
────────────────────────────
Business stakeholder: "Please confirm the order"
↓ (translation)
Developer: "I'll update the status column of the Order table to 1"
# Problems:
# - A "translation" is needed between the business and the code
# - The concept of "confirm" disappears from the code
# - It is easy for perceptions to diverge when requirements change
✓ With a ubiquitous language
───────────────────────────
Business stakeholder: "Please confirm the order"
↓ (as-is)
Developer: "I'll call Order.confirm()"
# Benefits:
# - The business and the code are expressed in the same words
# - You can understand the business flow by reading the code
# - The whole team shares the same understanding
A practical example of the ubiquitous language
Business language Code
"Confirm an order" → Order.confirm()
"Cancel an order" → Order.cancel()
"Add a product" → Order.addItem()
"Calculate the shipping fee" → Order.calculateShippingFee()
# The business language and the code match
The ubiquitous language is not a "business glossary." It is a living language that everyone on the team uses every day. It is important to keep using the same words across meetings, code, and documentation.
The bounded context
A bounded context is the range within which a particular domain model is valid. You logically separate a large system and keep a consistent model within each context.
Why a bounded context is needed
The same word can mean different things depending on the context. The bounded context solves this problem.
The meaning of the word "product" differs by context
────────────────────────────────────
Order context:
Product = the purchase target included in an order line
- Product ID
- Product name
- Price
- Quantity
Inventory context:
Product = stock managed in a warehouse
- Product ID
- Stock quantity
- Warehouse location
- Expected arrival date
Catalog context:
Product = product information shown to the customer
- Product ID
- Product description
- Images
- Category
# Even for the same "product," the information each context should hold differs
For beginners: how to think about a bounded context
A bounded context is like "the scope of work for each department."
A real-world example
────────────
Sales department:
"Customer" = a business partner that generates revenue
→ contract amount, transaction history, contact-person information
Support department:
"Customer" = the party you provide support to
→ inquiry history, product information, status of responses
# Even for the same word "customer," each department's concerns differ
An example of separating contexts
Each context has its own independent domain model.
In a small system, it is fine to handle everything in a single context. You only need to consider separating contexts once the system becomes complex.
From the next chapter, we look at each concept in detail. Let's start with value objects.
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 your understanding deepens as you actually write code. Let's get used to them gradually through the implementation examples in the following chapters.