Domain-driven
design:
Tactical design
Eleonora Ciceri - 28 January 2022
1
Is domain-driven always necessary?
No! Domain-driven may bring additional costs of software management, thus
it is better to offer CRUD interfaces whenever:
- the model is too simple (no logic, no behavior)
- the business logic is too simple
2
When to use domain models
The domain model pattern is intended to cope with cases of complex
business logic:
- complicated state transitions
- business rules
- invariants (i.e., rules that have to be protected at all times)
3
What is a domain model?
A domain model is an object model of the domain that incorporates both
behavior and data.
The building blocks of such an object model are:
- value objects
- entities
- aggregates
- domain events
- domain services
4
Value objects
5
What is a value object?
A value object is an object that can be identified by the composition of its
values. For example, consider a color object:
- The composition of the values of the three fields
defines a color
- Changing the value of a field results in a new color
- No two colors can have the same values
- Two instances of the same color must have the
same values.
6
class Color {
int red;
int green;
int blue;
}
“A red color and another red color are the same color”
Value objects and identifiers
Some will say “but hey, the ID is missing!”
The colorId field is not only redundant, but creates
opening for bugs: you could create two rows with
same red, green and blue and different ID, and
comparing the values of colorId would not reflect
that this is the same color
7
class Color {
int colorId;
int red;
int green;
int blue;
}
Using the ubiquitous language (1)
This is NOT the right way of implementing this
class! Using only primitive data types (primitive
obsession code smell) leads to:
- duplicated validation logic
- difficulty in enforcing calling the validation
logic before objects are used
An alternative implementation that uses the
ubiquitous language is this one
8
class Person {
String firstName;
String lastName;
String
phoneNumber;
String email;
}
class Person {
FullName name;
PhoneNumber phoneNumber;
EmailAddress email;
}
Using the ubiquitous language (2)
Advantages:
- We are using value objects (e.g., PhoneNumber or EmailAddress) to
increase clarity of the code. Value objects make the intent clear, even
with shorter variable names
- There is no need to validate the values before the assignment, as the
validation logic resides in the value object themselves
Value objects express the business domain’s concepts: they make the code
speak the ubiquitous language
9
Using the ubiquitous language (3)
For instance, going back to our Color example:
With this implementation, the value object:
- encapsulates all of the business logic that manipulates the data
- produces new instances of the value object
10
Color red = Color.fromRGB(255,0,0);
Color green = Color.green();
Color yellow = red.mixWith(green);
Implementation of a value object
Since changing a field of a
value object results in a
different value object, value
objects are implemented as
immutable objects
11
class Color {
public final int red;
public final int green;
public final int blue;
private Color(int r, int g, int b) {
this.red = r;
this.green = g;
this.blue = b;
}
public Color mixWith(Color other) {
return new Color(
Math.min(this.red + other.red, 255),
Math.min(this.green + other.green, 255),
Math.min(this.blue + other.blue, 255)
);
}
}
Equality of value objects
The equality of value objects is based on their values rather than on an ID or
reference. Hence, it is important to properly implement the equality check:
12
public bool equals(Color other) {
return this.red == other.red &&
this.green == other.green &&
this.blue == other.blue;
}
When to use a value object?
Whenever you can!
Value objects:
- make the code more expressive
- encapsulate business logic that tend to spread apart
- are free of side effect and are thread safe, as they are immutable
You can use value objects as properties of entities (see next section)
13
Entities
14
What is an entity?
An entity is the opposite of a value object. It requires an explicit
identification field to distinguish between the different instances of the
entity
PersonId and FullName are value objects. Specifically, PersonId can use any
underlying data type that fit the business domain needs: the if can be a GUID,
a number, a string or a domain-specific value such as the fiscal code
15
class Person {
public final PersonId id;
public FullName name;
}
Entities and identifiers
The central requirements for the identification field are
- it should be unique for each instance of the entity
- it should remain immutable throughout the entity’s lifecycle
Note: while value objects are immutable, entities are NOT immutable and are
expected to change!
16
Aggregates
17
What is an aggregate?
An aggregate is a single entity or a cluster of entities and value objects that must
remain transactionally consistent throughout the aggregate’s lifetime
The goal of this pattern is to protect the consistency of its data, even though the
aggregate’s data is mutable
18
Domain invariants and aggregates
A domain invariant is a statement or rule that must always be adhered to
An aggregate’s public interface is responsible for validating the input and
enforcing all of the relevant business rules and invariants
This ensures that all business logic related to the aggregate is implemented in
one place: the aggregate itself
19
Example. Winning auction bids must always be placed before the auction ends
Methods modifying the state of an aggregate
A method changing the state of an aggregate is called command, as it is a
“command to do something”
Here is an example of implementation:
20
class Ticket {
...
public void addMessage(Message message) {
this.messages.append(message);
}
}
The aggregate root
Since an aggregate represents a hierarchy of entities, only one of them should
be designated as the aggregate’s public interface, i.e., the one accepting
commands – the aggregate root
21
Message
N
Attachment
Ticket
N
TicketId
What to include (and not include) in an aggregate
The aggregate has to be kept as small as possible, including only objects that
are required to be in a strongly consistent state by the aggregate’s business
logic:
22
Message
N
Attachment
Ticket
N
Customer
Product
class Ticket {
TicketId id;
List<ProductId> products;
List<Message> messages;
UserId customer;
UserId assignedAgent;
}
TicketId
N
Agent
References to other aggregates
All the information that is not required by the aggregate’s business logic to be
strongly consistent can be eventually consistent and can be part of other
aggregates
23
Message
N
Attachment
Ticket
N
Customer
Product
TicketId
N
Agent
Consistency enforcement in an aggregate
Since an aggregate’s state can be mutated, it creates an opening for multiple
ways in which its data can become corrupted
The aggregate’s logic has to validate all incoming modifications and ensure
that the changes do not contradict its business rules
How to ensure consistency?
- We allow only the aggregate’s business logic to modify its state
- All processes or objects external to the aggregate are only allowed to read
the aggregate’s state
24
Aggregates and versioning (1)
If multiple processes concurrently update the same aggregate, we have to
prevent the latter transaction from blindly overwriting the changes committed
by the first one
Hence, the database used for storing aggregates has to support concurrency
management. In its simplest form, an aggregate should hold a version field
incremented after each update:
25
class Ticket {
TicketId id;
int version;
}
Aggregates and versioning (2)
When committing a change to the database, we ensure that the version that is
being overwritten matches the one that was originally read:
Changes are applied only if the current version equals the one that was read
prior to applying changes to the aggregate’s state
26
UPDATE tickets
SET status = new_status,
version = expected_version + 1
WHERE id=ticket_id AND version = expected_version
Domain events
27
Domain events
A domain event is a message describing a significant event that has occurred
in the business domain
Examples:
- Ticket assigned
- Ticket escalated
- Message received
Since domain events describe something that has already happened, their
names should be formulated in the past tense
28
What does a domain event contain?
The goal of a domain event is to describe what has happened in the business
domain and provide all the necessary data related to the event
29
{
“event-id”: “an-event-id”,
“event-type”: “ticket-escalated”,
“ticket-id”: “a-ticket-id”,
“escalation-reason”: “missed-sla”,
“timestamp”: “2021-12-21T11:49”
}
Domain events as public interface
Domain events are part of an aggregate’s public interface: an aggregate
publishes domain events
Other processes, aggregates or even external systems can subscribe to (and
execute their own logic in response to) the domain events
30
Domain events vs Events to other services
Be aware! A domain event is not necessarily made to communicate things to
other architectural components (e.g., microservices)!
This may happen, but…
- domain events are useful for the domain and its functioning
- don’t force yourself in making the “domain events set” match the “events
to other services”, it pollutes the domain with architectural concerns or
events that are not really needed for its functioning
31
Domain services
32
Domain service
A domain service is a stateless object that implements the business logic
33
class ResponseTimeCalculationService {
ResponseTimeframe calculateAgentResponseDeadline(UserId agentId, Priority priority,
bool escalated, DateTime startTime) {
Policy policy = departmentRepository.getDepartmentPolicy(agentId);
Time maxProcTime = policy.getMaxResponseTimeFor(priority);
if (escalated) {
maxProcTime = maxProcTime * policy.escalationFactor;
}
List<Shifts> shifts = departmentRepository.getUpcomingShifts(agentId, startTime,
startTime.add(policy.maxAgentResponseTime));
return calculateTargetTime(maxProcTime, shifts);
}
}
Domain services and transactions
Domain services make it easy to coordinate the work of multiple
aggregates
In any case, is important to remember that:
- reading from multiple aggregates is possible, and needed to implement
the business logic
- writing in repositories is constrained: we can modify only one instance of
an aggregate in one database transaction!
34
Domain service is just a stateless object
A domain service has nothing to do with
- microservices
- service-oriented architecture
- any other use of the word service in software engineering
35
References
36
References
[1] Vlad Khononov, Learning Domain-Driven Design - Aligning Software
Architecture and Business Strategy, O’Reilly, 2022
[2] Scott Millett, Patterns, Principles and Practices of Domain Driven Design,
Wrox, 2015
[3] Vernon & Evans, Implementing Domain-Driven Design, Addison-Wesley
Professional, 2013
37

DDD - 2 - Domain Driven Design: Tactical design.pdf

  • 1.
  • 2.
    Is domain-driven alwaysnecessary? No! Domain-driven may bring additional costs of software management, thus it is better to offer CRUD interfaces whenever: - the model is too simple (no logic, no behavior) - the business logic is too simple 2
  • 3.
    When to usedomain models The domain model pattern is intended to cope with cases of complex business logic: - complicated state transitions - business rules - invariants (i.e., rules that have to be protected at all times) 3
  • 4.
    What is adomain model? A domain model is an object model of the domain that incorporates both behavior and data. The building blocks of such an object model are: - value objects - entities - aggregates - domain events - domain services 4
  • 5.
  • 6.
    What is avalue object? A value object is an object that can be identified by the composition of its values. For example, consider a color object: - The composition of the values of the three fields defines a color - Changing the value of a field results in a new color - No two colors can have the same values - Two instances of the same color must have the same values. 6 class Color { int red; int green; int blue; } “A red color and another red color are the same color”
  • 7.
    Value objects andidentifiers Some will say “but hey, the ID is missing!” The colorId field is not only redundant, but creates opening for bugs: you could create two rows with same red, green and blue and different ID, and comparing the values of colorId would not reflect that this is the same color 7 class Color { int colorId; int red; int green; int blue; }
  • 8.
    Using the ubiquitouslanguage (1) This is NOT the right way of implementing this class! Using only primitive data types (primitive obsession code smell) leads to: - duplicated validation logic - difficulty in enforcing calling the validation logic before objects are used An alternative implementation that uses the ubiquitous language is this one 8 class Person { String firstName; String lastName; String phoneNumber; String email; } class Person { FullName name; PhoneNumber phoneNumber; EmailAddress email; }
  • 9.
    Using the ubiquitouslanguage (2) Advantages: - We are using value objects (e.g., PhoneNumber or EmailAddress) to increase clarity of the code. Value objects make the intent clear, even with shorter variable names - There is no need to validate the values before the assignment, as the validation logic resides in the value object themselves Value objects express the business domain’s concepts: they make the code speak the ubiquitous language 9
  • 10.
    Using the ubiquitouslanguage (3) For instance, going back to our Color example: With this implementation, the value object: - encapsulates all of the business logic that manipulates the data - produces new instances of the value object 10 Color red = Color.fromRGB(255,0,0); Color green = Color.green(); Color yellow = red.mixWith(green);
  • 11.
    Implementation of avalue object Since changing a field of a value object results in a different value object, value objects are implemented as immutable objects 11 class Color { public final int red; public final int green; public final int blue; private Color(int r, int g, int b) { this.red = r; this.green = g; this.blue = b; } public Color mixWith(Color other) { return new Color( Math.min(this.red + other.red, 255), Math.min(this.green + other.green, 255), Math.min(this.blue + other.blue, 255) ); } }
  • 12.
    Equality of valueobjects The equality of value objects is based on their values rather than on an ID or reference. Hence, it is important to properly implement the equality check: 12 public bool equals(Color other) { return this.red == other.red && this.green == other.green && this.blue == other.blue; }
  • 13.
    When to usea value object? Whenever you can! Value objects: - make the code more expressive - encapsulate business logic that tend to spread apart - are free of side effect and are thread safe, as they are immutable You can use value objects as properties of entities (see next section) 13
  • 14.
  • 15.
    What is anentity? An entity is the opposite of a value object. It requires an explicit identification field to distinguish between the different instances of the entity PersonId and FullName are value objects. Specifically, PersonId can use any underlying data type that fit the business domain needs: the if can be a GUID, a number, a string or a domain-specific value such as the fiscal code 15 class Person { public final PersonId id; public FullName name; }
  • 16.
    Entities and identifiers Thecentral requirements for the identification field are - it should be unique for each instance of the entity - it should remain immutable throughout the entity’s lifecycle Note: while value objects are immutable, entities are NOT immutable and are expected to change! 16
  • 17.
  • 18.
    What is anaggregate? An aggregate is a single entity or a cluster of entities and value objects that must remain transactionally consistent throughout the aggregate’s lifetime The goal of this pattern is to protect the consistency of its data, even though the aggregate’s data is mutable 18
  • 19.
    Domain invariants andaggregates A domain invariant is a statement or rule that must always be adhered to An aggregate’s public interface is responsible for validating the input and enforcing all of the relevant business rules and invariants This ensures that all business logic related to the aggregate is implemented in one place: the aggregate itself 19 Example. Winning auction bids must always be placed before the auction ends
  • 20.
    Methods modifying thestate of an aggregate A method changing the state of an aggregate is called command, as it is a “command to do something” Here is an example of implementation: 20 class Ticket { ... public void addMessage(Message message) { this.messages.append(message); } }
  • 21.
    The aggregate root Sincean aggregate represents a hierarchy of entities, only one of them should be designated as the aggregate’s public interface, i.e., the one accepting commands – the aggregate root 21 Message N Attachment Ticket N TicketId
  • 22.
    What to include(and not include) in an aggregate The aggregate has to be kept as small as possible, including only objects that are required to be in a strongly consistent state by the aggregate’s business logic: 22 Message N Attachment Ticket N Customer Product class Ticket { TicketId id; List<ProductId> products; List<Message> messages; UserId customer; UserId assignedAgent; } TicketId N Agent
  • 23.
    References to otheraggregates All the information that is not required by the aggregate’s business logic to be strongly consistent can be eventually consistent and can be part of other aggregates 23 Message N Attachment Ticket N Customer Product TicketId N Agent
  • 24.
    Consistency enforcement inan aggregate Since an aggregate’s state can be mutated, it creates an opening for multiple ways in which its data can become corrupted The aggregate’s logic has to validate all incoming modifications and ensure that the changes do not contradict its business rules How to ensure consistency? - We allow only the aggregate’s business logic to modify its state - All processes or objects external to the aggregate are only allowed to read the aggregate’s state 24
  • 25.
    Aggregates and versioning(1) If multiple processes concurrently update the same aggregate, we have to prevent the latter transaction from blindly overwriting the changes committed by the first one Hence, the database used for storing aggregates has to support concurrency management. In its simplest form, an aggregate should hold a version field incremented after each update: 25 class Ticket { TicketId id; int version; }
  • 26.
    Aggregates and versioning(2) When committing a change to the database, we ensure that the version that is being overwritten matches the one that was originally read: Changes are applied only if the current version equals the one that was read prior to applying changes to the aggregate’s state 26 UPDATE tickets SET status = new_status, version = expected_version + 1 WHERE id=ticket_id AND version = expected_version
  • 27.
  • 28.
    Domain events A domainevent is a message describing a significant event that has occurred in the business domain Examples: - Ticket assigned - Ticket escalated - Message received Since domain events describe something that has already happened, their names should be formulated in the past tense 28
  • 29.
    What does adomain event contain? The goal of a domain event is to describe what has happened in the business domain and provide all the necessary data related to the event 29 { “event-id”: “an-event-id”, “event-type”: “ticket-escalated”, “ticket-id”: “a-ticket-id”, “escalation-reason”: “missed-sla”, “timestamp”: “2021-12-21T11:49” }
  • 30.
    Domain events aspublic interface Domain events are part of an aggregate’s public interface: an aggregate publishes domain events Other processes, aggregates or even external systems can subscribe to (and execute their own logic in response to) the domain events 30
  • 31.
    Domain events vsEvents to other services Be aware! A domain event is not necessarily made to communicate things to other architectural components (e.g., microservices)! This may happen, but… - domain events are useful for the domain and its functioning - don’t force yourself in making the “domain events set” match the “events to other services”, it pollutes the domain with architectural concerns or events that are not really needed for its functioning 31
  • 32.
  • 33.
    Domain service A domainservice is a stateless object that implements the business logic 33 class ResponseTimeCalculationService { ResponseTimeframe calculateAgentResponseDeadline(UserId agentId, Priority priority, bool escalated, DateTime startTime) { Policy policy = departmentRepository.getDepartmentPolicy(agentId); Time maxProcTime = policy.getMaxResponseTimeFor(priority); if (escalated) { maxProcTime = maxProcTime * policy.escalationFactor; } List<Shifts> shifts = departmentRepository.getUpcomingShifts(agentId, startTime, startTime.add(policy.maxAgentResponseTime)); return calculateTargetTime(maxProcTime, shifts); } }
  • 34.
    Domain services andtransactions Domain services make it easy to coordinate the work of multiple aggregates In any case, is important to remember that: - reading from multiple aggregates is possible, and needed to implement the business logic - writing in repositories is constrained: we can modify only one instance of an aggregate in one database transaction! 34
  • 35.
    Domain service isjust a stateless object A domain service has nothing to do with - microservices - service-oriented architecture - any other use of the word service in software engineering 35
  • 36.
  • 37.
    References [1] Vlad Khononov,Learning Domain-Driven Design - Aligning Software Architecture and Business Strategy, O’Reilly, 2022 [2] Scott Millett, Patterns, Principles and Practices of Domain Driven Design, Wrox, 2015 [3] Vernon & Evans, Implementing Domain-Driven Design, Addison-Wesley Professional, 2013 37