Part II — Domain-Driven Design · Chapter 3

The building blocks

~35 min read4 interactive widgets3 plates

In this chapter

  1. From concepts to types
  2. The seven blocks at a glance
  3. Entities and value objects
  4. Aggregate roots
  5. Factories
  6. Repositories
  7. Services
  8. Domain events
  9. Choosing a block, and what the choice drags in
  10. Check your understanding

1. From concepts to types

The previous chapter ended with a glossary and a rule of thumb. This chapter supplies the machinery that turns the glossary into a typed model. The mapping is stated as a two-column correspondence:

Domain Model
Conceptmodelling →Type
instancemodelling →object

Each concept from each context shall become a type in the model, where type means class, interface, structure, ADT and so on — it depends on what the programming language has to offer. And then the sentence that gives this chapter its purpose: use building blocks as archetypes, and let them guide and constrain your design.

The workflow of Chapter 2 continues with three more steps:

Key idea

A building block is not a label you attach after the fact. It is a constraint generator: once you decide that Customer is an entity, you have simultaneously decided that it needs an identifier type, that equality compares identity, that it may be mutable, and that something must be responsible for storing and creating it. The design cascades.

2. The seven blocks at a glance

The overview slide lists seven archetypes. Learn this table first; the rest of the chapter is its expansion.

Building blockOne-line definitionIdentityState
EntityObjects with an identifierYes, inherent and unchangingMay be stateful
Value ObjectObjects without identityNo — identified by attributesMust be stateless (immutable design)
Aggregate RootCompound objectsYes (it is an entity)Usually stateful, guards consistency
Domain EventObjects modelling a relevant event (notifications)No — value-likeTime-stamped, immutable
ServiceObjects providing stateless functionalitiesNoStateless
RepositoryObjects providing storage facilitiesNoStateful (stored objects, DB connections)
FactoryObjects creating other objectsNoStateless

3. Entities and value objects

The lecture defines the pair with a genus-differentia definition, which is the cleanest way to hold them apart:

Two quick modelling examples

SituationModellingWhy
Seats in a classroomValue objectsOne seat is as good as another: they are interchangeable
Attendees of a classEntitiesEach attendee is a distinct individual, whatever their attributes
Numbered seats on a planeEntitiesThe number is an identity: seat 14C is not seat 14D
Unnumbered seats on a planeValue objectsWithout a number there is nothing to tell one from another

The plane example is the instructive one: the very same real-world object is an entity or a value object depending on the domain's own rules, not on its physical nature. Free seating erases the identity; assigned seating creates it.

Constraints, block by block

  • Identified by their attributes: equality compares attributes alone.
  • Must be stateless ⇒ better to use an immutable design: read-only properties, no state-changing methods.
  • May be implemented as structures in .NET, data classes in Kotlin, Scala and Python, records in Java.
  • On the JVM, must implement equals() and hashCode(), and the implementation must compare the objects' attributes.
  • They have an inherent identity, which never changes during their lifespan. Common modelling: an identifier attribute, of some value type.
  • Equality compares identity.
  • Can be stateful ⇒ may have a mutable design: modifiable properties, state-changing methods.
  • May be implemented via classes in most languages.
  • On the JVM, must implement equals() and hashCode(), comparing at least the objects' identifiers.
Watch out

"Value objects must be stateless" is stronger than "value objects happen to be immutable in my code". If a value object can mutate, two references that were interchangeable a moment ago silently stop being interchangeable, and every collection that hashed it becomes wrong. This is the concrete reason behind the equals()/hashCode() requirement on the JVM.

4. Aggregate roots

An aggregate root is defined by four statements:

Constraints

The worked example in the lecture is an Order that must refer to its buyer. The link between Order and Buyer is implemented by letting the order hold a reference to the BuyerID — the identifier, a value object — and not to the buyer object itself. The aggregate boundary stays sealed, and the reference survives being serialised, stored, or sent across a context boundary.

5. Factories

Factories are objects aimed at creating other objects. Their purpose is threefold:

A remark worth memorising: DDD's notion of factory is quite loose. Formally, DDD's Factories ⊇ GOF's Factories ∪ Builders ∪ ... — the DDD block is a superset of the Gang of Four patterns that happen to create objects.

Constraints

The example given in the lecture is a CustomerFactory exposing a method to compute VAT numbers, methods for creating person customers (from a tax code, or from name, surname, birth date and birth place) and a method for creating company customers. Notice how the factory's signature list is itself a piece of domain knowledge: it says these are the legitimate ways a customer comes into existence.

6. Repositories

Repositories are objects mediating the persistent storage and retrieval of other objects. Their purposes:

Three remarks accompany them. Repositories may exploit factories for turning retrieved data into objects. If properly engineered, they avoid the lock-in effect for database technologies. And their design and implementation may require thinking about the architecture, the infrastructure and the expected load.

Constraints

Key idea

A repository is the place where the domain stops and the infrastructure begins. That is exactly why the lecture later calls repository types a typical example of an anti-corruption layer for database technologies (Chapter 4): they translate between a vocabulary chosen by the domain and one chosen by a vendor.

7. Services

Services are functional objects encapsulating the business logic of the software, for example operations spanning several entities, objects and aggregates. Their purposes:

Two remarks refine the picture. Services may be exposed via ReSTful API. And there is a design asymmetry worth quoting:

Services should be designed keeping current use cases into account (design services to be purpose-specific); entities and objects should support future use cases too (design entities and objects to be general purpose).

Constraints

interface OrderManagementService {
    void performOrder(Order order);
    void notifyOrderPerformed(OrderEventArgs event);
}

In the lecture's example the service handles the Order aggregate and updates the OrderStore repository, while the order itself is composed of an OrderID (value object), a Customer (entity) and several Products (entities). One diagram, five different blocks: the service is where they meet.

8. Domain events

A domain event is a value-like object capturing some domain-related event — an observable variation in the domain which is relevant to the software. The lecture adds a precision that is easy to miss and important to state at an oral exam: only the event notification or description is reified into a type. The event itself is something that happens in the world; what enters the model is its description.

Purpose and relations

Constraints

Teacher's suggestion

Prefer neutral names for event classes in the model: OrderEventArgs instead of OrderPerformedEventArgs, OrderEvent instead of OrderPerformedEvent. The reason: the same OOP type may be used to represent different events — orderIssued, orderConfirmed, orderCancelled and so on.

interface OrderEventArgs {           // domain event
    OrderID getID();                 // value object
    CustomerID getCustomer();        // value object
    Date getTimestamp();             // time-stamped, as every domain event
    Dictionary<ProductID, Long> getAmounts();
}

Note what the event carries: identifiers, not objects. A notification that travels across contexts and possibly across a message broker cannot drag an aggregate behind it — the same discipline seen in the aggregate rule, applied to messages.

9. Choosing a block, and what the choice drags in

The chapter closes where it began: the choice of a building block depends on the nature of the concept, or on the properties of its instances, and it may lead to the identification of other concepts and models. The widget below walks the questions in the order the lecture implies.

For the exam

The classic question is entity versus value object, and the classic answer is the genus-differentia definition plus its consequences: equality compares identity vs equality compares attributes; may be mutable vs must be stateless; class vs record/data class/struct; and, on the JVM, equals()/hashCode() over the identifier vs over all attributes. Be ready to add the plane-seat example, because it shows that the classification is a property of the domain, not of the object.

Check your understanding

Give the genus-differentia definition of entity and value object.

Genus: both can be used to model elementary concepts. Differentia: entities have an explicit identity, value objects are interchangeable (identified by their attributes).

Why can a plane seat be either an entity or a value object?

Because the domain decides. Numbered seats have an identity that the domain cares about (14C is not 14D) and are therefore entities. Unnumbered seats are interchangeable and are therefore value objects. The physical object is the same; the modelling follows the domain's rules, not the object's material nature.

What must a value object implement on the JVM, and over what?

equals() and hashCode(), and the implementation must compare the objects' attributes. For an entity the same two methods are required, but they must compare at least the identifiers.

Define an aggregate root and state its four properties.

A composite entity aggregating related entities and value objects. It guarantees the consistency of the objects it contains; it mediates their usage from the outside, acting as a facade; and outside objects should avoid holding references to its composing objects.

May a component of one aggregate reference a component of another?

No — that is precisely the rule that makes an aggregate a root. The notable exception is a reference to the identifier of another aggregate: in the lecture's example, Order holds a BuyerID, not a Buyer.

How does DDD's notion of factory relate to the GOF patterns?

It is looser and larger: DDD's Factories ⊇ GOF's Factories ∪ Builders ∪ .... What matters is the role — encapsulating creation logic, enforcing invariants, selecting an implementation dynamically while hiding the choice — not the specific pattern used to play it.

What does a repository store, and at what granularity?

It stores and retrieves aggregate roots as wholes, supporting CRUD operations on them and wrapping common queries. It hides the database technology, may perform ORM, and on the JVM should return Iterable, Collection or Stream so that retrieval can be lazy.

Which two concerns must a non-trivial repository handle?

Consistency in spite of concurrent access and support for complex transactions. Both are consequences of the fact that a repository is stateful and shared.

State the asymmetry between designing services and designing entities.

Services should be designed for the current use cases, that is, purpose-specific. Entities and objects should support future use cases too, that is, general purpose. Business logic is volatile; the domain vocabulary is not.

Why does the lecture suggest naming event types neutrally?

Because the same OOP type may represent several different events — orderIssued, orderConfirmed, orderCancelled. Naming the type OrderEventArgs rather than OrderPerformedEventArgs keeps it reusable for the whole family of variations of that concept.

What exactly gets reified when you model a domain event?

Only the event notification or description, as a time-stamped, value-like object — typically a record or data class carrying identifiers and a timestamp. The occurrence itself stays in the world; what enters the model is its description, which is why domain events travel well across contexts (via a message broker or queue).

What does "the choice of a building block may lead to the identification of other concepts" mean in practice?

That the archetypes come with obligations: entities may need value objects as identifiers, repositories to be stored and factories to be created; aggregates may be composed of entities or value objects. Classifying one concept therefore uncovers the next ones to model.