Compiled on: 2025-06-30 — printable version
This is an opinionated synthesis and interpretation of the seminal book from Eric Evans
A much quicker overview is from Martin Flower
The Wikipedia page is also quite informative
There exist many programming languages…
There exist many object-oriented design patterns…
There exist many software engineering best practices…
What’s the criterion to choose if and when to adopt languages / patterns / best / practices?
Here we present domain-driven design (DDD)
It consists of principles, best practices, and patterns leading design
Major benefits:
Definition of domain:
Remarks:
Examples of domains and the contexts composing them
Software will represent a solution to a problem in some business domain
Words do not have meaning per se, but rather w.r.t. a domain
Software should stick to the domain, at any moment
Functionalities, structure, and UX should mirror the domain too
as both developers and users are (supposed to be) immersed in the domain
Domain: the reference area of knowledge
Context: a portion of the domain
Model: a reification of the domain in software
Ubiquitous Language: language used by domain experts and mirrored by the model
A well established sphere of knowledge, influence or activity
A portion of the domain with a clear boundary:
(i) relying on a sub-set of the concepts of the domain
(ii) where words/names have a unique, precise meaning
(iii) clearly distinguishable from other contexts
Set of software abstractions mapping relevant concepts of the domain
- A language structured around the domain model
- used by all people involved into the domain
- which should be used in the software
- in such a way that their semantics is preserved
underlying assumption:
commonly reified into a glossary of terms
used to name software components
Identify the domain, give a name to it
Identify the main contexts within the domain, giving them names
Identify the actual meaning of relevant words from the domain, and track them into a glossary
possibly, by interacting with experts
without assuming you already know the meaning of words
keep in mind that the meaning of words may vary among contexts
Adhere to the language, use it, make it yours
Draw a context map tracking
Model the software around the ubiquitous language
Abstract
Actual
Domain $\rightarrow$ Model
Each concept from each context shall become a type in the model
Use building blocks as archetypes
(continued)
Chose the most adequate building block for each concept
The building block dictates how to design the type corresponding to the concept
The choice of building block may lead to the identification of other concepts / models
Entity: objects with an identifier
Value Object: objects without identity
Aggregate Root: compound objects
Domain Event: objects modelling relevant event (notifications)
Service objects: providing stateless functionalities
Repository: objects providing storage facilities
Factory: objects creating other objects
Genus-differentia definition:
- genus: both can be used to model elementary concepts
- differentia: entities have an explicit identity, value objects are interchangeable
Seats in classroom may be modelled as value-objects
Attendees of a class may be modelled as entities
Numbered seats $\rightarrow$ entities
otherwise $\rightarrow$ value objects
__eq__()
and __hash__()
on JVM
__eq__()
and __hash__()
on JVM
interface Customer { + id: CustomerID {readonly} + name: str + email: str } note left: Entity
interface CustomerID { + value {readonly} } note right: Value Object
interface TaxCode { + value: str } note left: Value Object
interface VatNumber { + value: int } note right: Value Object
VatNumber -d-|> CustomerID TaxCode -d-|> CustomerID
Customer *-r- CustomerID
A composite entity, aggregating related entities/value objects
It guarantees the consistency of the objects it contains
It mediates the usage of the composing objects from the outside
Outside objects should avoid holding references to composing objects
They are usually compound entities
They can be or exploit collections to contain composing items
May be better implemented as classes in most programming languages
Must implement __eq__()
and __hash__()
on JVM (as any other entity)
Components of an aggregate should not hold references to components of other aggregates
(notice the link between Order
and Buyer
implemented by letting the Order
hold a reference to the BuyerID
)
Objects aimed at creating other objects
Factories encapsulate the creation logic for complex objects
They ease the enforcement of invariants
They support dynamic selection of the most adequate implementation
They are usually identity-less and state-less objects
May be implemented as classes in most OOP languages
Provide methods to instantiate entities or value objects
Usually they require no mutable field/property
No need to implement __eq__()
and __hash__()
on JVM
interface CustomerID
interface TaxCode
interface VatNumber
interface Customer
Customer “1” *– “1” CustomerID
VatNumber -u-|> CustomerID TaxCode -u-|> CustomerID
interface CustomerFactory { + compute_vat_number(name: str, surname: str, birth_date: date, birth_place: str) -> VatNumber .. + new_customer_person(code: TaxCode, full_name: str, email: str) -> Customer + new_customer_person(name: str, surname: str, birth_date: date, birth_place: str, email: str) -> Customer .. + new_costumer_company(code: VatNumber, full_name: str, email: str) -> Customer } note bottom of CustomerFactory
CustomerFactory -r-> VatNumber: creates CustomerFactory -u-> Customer: creates
Objects mediating the persistent storage/retrieval of other objects
They are usually identity-less, stateful, and composed objects
May be implemented as classes in most OOP languages
Provide methods to
Iterable
, Collection
, or Stream
on JVMNon-trivial implementations should take care of
interface CustomerID
interface Customer
Customer “1” *-u- “1” CustomerID
interface CustomerRegistry { + get_all_customers() -> Iterable[Customer] .. + find_customer_by_id(id: CustomerID) -> Customer + find_customer_by_name(name: str) -> Iterable[Customer] + find_customer_by_email(email: str) -> Iterable[Customer] .. + add_new_customer(customer: Customer) + update_customer(customer: Customer) + remove_customer(customer: Customer) }
CustomerRegistry “1” o–> “N” Customer: contains CustomerRegistry –> CustomerID: exploits
Functional objects encapsulating the business logic of the software
e.g. operations spanning through several entities, objects, aggregates, etc.
They are usually identity-less, stateless objects
May be implemented as classes in OOP languages
Commonly provide procedures to do business-related stuff
Non-trivial implementations should take care of
interface OrderManagementService { + perform_order(order: Order) }
interface Order { + id: OrderID {readonly} + customer: Customer + timestamp: datetime + amounts: dict[Product, long] }
interface OrderID
interface Customer
interface Product
interface OrderStore
Order “1” *-r- “1” OrderID Order “1” *-d- “1” Customer Order “1” *-u- “N” Product OrderStore “1” *– “N” Order
OrderManagementService ..> Order: handles OrderManagementService ..> OrderStore: updates
note bottom of OrderStore: repository note top of Order: entity note right of OrderID: value object note right of Product: entity note right of Customer: entity note top of OrderManagementService: service
OrderID -u[hidden]- Product OrderID -d[hidden]- Customer
A value-like object capturing some domain-related event
(i.e., an observable variation in the domain, which is relevant to the software)
Strong relation with the observer pattern (i.e. publish-subscribe)
Strong relation with the event sourcing approach (described later)
Strong relation with the CQRS pattern (described later)
They are usually time-stamped value objects
May be implemented as data-classes or records
They represent a relevant variation in the domain
Event sources & listeners shall be identified too
Infrastructural components may be devoted to propagate events across contexts
[Teacher’s Suggestion]: prefer neutral names for event classes in the model
OrderEvent
instead of OrderPerformedEventArgs
OrderEvent
instead of OrderPerformedEvent
orderIssued
, orderConfirmed
, orderCancelled
, etc.interface OrderManagementService { + perform_order(order: Order) .. + notify_order_performed(event: OrderEvent) }
interface OrderEvent { + id: OrderID {readonly} + customer: CustomerID {readonly} + timestamp: datetime {readonly} + amounts: dict[ProductID, float] {readonly} }
interface OrderID
interface CustomerID
interface ProductID
OrderEvent “1” *-u- “1” OrderID OrderEvent “1” *-r- “1” CustomerID OrderEvent “1” *-d- “N” ProductID
OrderEvent .. OrderManagementService
note left of OrderEvent: domain event note left of OrderID: value object note left of ProductID: value object note right of CustomerID: value object note right of OrderManagementService: service
Bounded Context: enforce a model’s boundaries & make them explicit
Context Map: providing a global view on the domain and its contexts
The boundary of a context and its software model should be explicit. This is helpful from several perspectives:
- technical (e.g., dependencies among classes/interfaces)
- physical (e.g., common database, common facilities)
- organizational (e.g. people/teams maintaining/using the code)
A map of all the contexts in a domain and their boundaries
- and their points of contact
- e.g. their dependencies, homonyms, false friends, etc.
- providing the whole picture of the domain
Clearly identify & represent boundaries among contexts
Avoid responsibility diffusion over a single context
Avoid changes in the model for problems arising outside the context
Enforce context’s cohesion via automated unit and integration testing
As the domain evolves, the software model should evolve with it
Yet, the domain rarely changes as a whole
Contexts-are bounded, but not isolated
Changes to a context, and its model may propagate to other context / models
Domain / model changes are critical and should be done carefully
Preserve the integrity of the model w.r.t. the domain
Minimise the potential impact / reach of changes
Each relation among 2 contexts usually involves 2 ends/roles:
Integration among contexts $\leftrightarrow$ interaction among teams
*trust $\approx$ willingness to collaborate + seek for stability
Best when: multiple contexts share the same team / organization / product
Key idea: factorise common portions of the model into a shared kernel
Upstream and downstream collaborate in designing / developing / maintaining the model
Keeping the kernel as small as possible is fundamental
Best when:
Key idea:
Customers may ask for features, suppliers will do their best to provide them
Suppliers shall warn before changing their model
Best when:
Key idea: downstream must conform to the upstream, reactively
Best when:
If upstream cannot be trusted, and interaction is pointless…
… downstream must defend from unexpected / unanticipated change
The upstream’s model is then reverse engineered & adapted
DDD does not enforce a particular architecture
Any is fine as long the model is integer
Layered architectures are well suited to preserve models’ integrity
Here we focus on the hexagonal architecture, a particular case of layered architecture
Domain layer: contains the domain model (entities, values, events, aggregates, etc.)
Application layer: contains services providing business logic
Presentation layer: provides conversion facilities to/from representation formats
Storage layer: supports persistent storage/retrieval of domain data
Interface layers (e.g. ReST API, MOM, View): let external entities access the software
Layering may be enforced in the code
By mapping layers into modules
A pattern where domain events are reified into time-stamped data and the whole evolution of a system is persistently stored
Historical data can be analysed, to serve several purposes
Past situations can be replayed
Enables complex event detection & reaction
Enables CQRS (described later)
Advanced pattern for building highly-scalable applications
It leverages upon event sourcing and layered architecture…
… to deliver reactive, eventual-consistent solutions where:
Split the domain and application layers to segregate read/write responsibilities
Read model (a.k.a. view or query model)
Write model (a.k.a. command model)
Whenever users are willing to perform an action into the system:
they create a command and forward it to the write model
the command is possibly validated & stored onto some database
Whenever users are willing to inspect/observe the system at time $t$:
they perform a query on the read model
commands up to time $t$ are assumed to be reified when reading
Reification: is the process of computing the state of the system at time $t$ by applying of commands recorded up to time $t$
If queries and commands are stored on different databases
Several, non-mutually-exclusive strategies:
In the context of domain driven design,
Invent a domain of choice, and a few relevant concepts in it to be modelled via the domain driven design approach. The model should include (at least) an entity, a value object, a repository, a domain event, a service. Factories are welcome too. Identify aggregate roots if any. Provide a textual description of the domain concepts, and a UML chart of the corresponding classes
Compiled on: 2025-06-30 — printable version