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 | |
|---|---|---|
| Concept | modelling → | Type |
| instance | modelling → | 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:
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.
The overview slide lists seven archetypes. Learn this table first; the rest of the chapter is its expansion.
| Building block | One-line definition | Identity | State |
|---|---|---|---|
| Entity | Objects with an identifier | Yes, inherent and unchanging | May be stateful |
| Value Object | Objects without identity | No — identified by attributes | Must be stateless (immutable design) |
| Aggregate Root | Compound objects | Yes (it is an entity) | Usually stateful, guards consistency |
| Domain Event | Objects modelling a relevant event (notifications) | No — value-like | Time-stamped, immutable |
| Service | Objects providing stateless functionalities | No | Stateless |
| Repository | Objects providing storage facilities | No | Stateful (stored objects, DB connections) |
| Factory | Objects creating other objects | No | Stateless |
The lecture defines the pair with a genus-differentia definition, which is the cleanest way to hold them apart:
| Situation | Modelling | Why |
|---|---|---|
| Seats in a classroom | Value objects | One seat is as good as another: they are interchangeable |
| Attendees of a class | Entities | Each attendee is a distinct individual, whatever their attributes |
| Numbered seats on a plane | Entities | The number is an identity: seat 14C is not seat 14D |
| Unnumbered seats on a plane | Value objects | Without 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.
equals() and hashCode(), and the implementation must compare the objects' attributes.equals() and hashCode(), comparing at least the objects' identifiers."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.
An aggregate root is defined by four statements:
equals() and hashCode() as any other entity; the implementation may take composing items into account.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.
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.
equals() and hashCode() on the JVM.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.
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.
Iterable, Collection or Stream.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.
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).
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.
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.
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.
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.
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.
Genus: both can be used to model elementary concepts. Differentia: entities have an explicit identity, value objects are interchangeable (identified by their attributes).
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.
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.
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.
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.
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.
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.
Consistency in spite of concurrent access and support for complex transactions. Both are consequences of the fact that a repository is stateful and shared.
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.
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.
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).
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.