Domain Driven Design Is Hard But... - Part 1
Last year, I was involved in a project in the financial industry. The project itself is relatively "simple" which allows users to submit documents to update their key personal information. I joined a project halfway where the project had already started a few months back. At first, I was overwhelmed by the various business jargons they use and what made it even harder was that the domain experts and developers do not speak the same language and domain experts themselves sometimes switched terms. The developers have already installed this translator in their minds to convert certain terms from domain experts to terms they use. In the codebase, it is even messier that both terms coexist and are scattered everywhere. As I started to grasp more concepts, I found even more problems which span beyond the team and it seemed to be an organisational problem. I can summarise the problems as the following few points:
- Domain experts and developers are in general not collaborating;
- The organisation tries to unify one giant single model and respective teams use their "smart" brains to avoid using this model;
- There are no relationships between upstream and downstream teams. In certain times, teams have to beg for other teams to deliver a certain feature.
There are other problems too but I would like to highlight these three which I think might exist in many companies. Honestly speaking, it was quite tough for me to be in that project in the beginning. I had to learn to translate from terms to terms to be able to understand a certain feature requirement. This made me start to wonder if there is a way to tackle these problems in large organisations. I stumbled upon this book and somehow memory serves me right as I vaguely overheard the book from colleagues' casual conversations over lunch.
In this blog, I will try to explain the concepts I learned from reading this book. I highly recommend every software developer to read the book and I hope this blog will at least get you motivated.
1. Ubiquitous Language
Eric uses the term ubiquitous to describe the language. I checked the definition of ubiquitous
Ubiquitous language is the distillation of knowledge crunching between development teams and domain experts. This process is interactive exploration that certain words would stand out and stick with the conversation going forward. Knowledge crunching should be facilitated by domain models in terms of UML diagrams or expressive diagrams mutually understood by both domain experts and development team. Domain models are the backbones of the ubiquitous language. Any new terms introduced in the ubiquitous language should reflect a change in the domain models. As this process goes deeper, the silhouette of the ubiquitous language would slowly be distilled.
I would like to point out one skill here when talking in the ubiquitous language, let's look at the following two examples:
- "If we give the Routing Service an origin, destination and arrival time, it can look up the stops. The cargo will have to make and...well, stick them in the database"
- "A Routing Service finds an Itinerary that satisfies a Route Specification"
The second statement is more concise, and captures more ubiquitous language. It doesn't talk about the details rather it focuses on abstraction. This reminds me of the Open-Close Principle when it comes to OOP design. We try to close on the contract of certain domain models' responsibility yet open to detailed implementation.
When I look back at the project, I wish I could have known this so that at least within our team, we would be able to speak the same language which causes less confusion. I would instead be more proactive in learning domain knowledge knowing that it helps on the software design. I always had this misconception that mastering the domain knowledge should be a business analyst's responsibility. To some extent, it is; however, as a developer it is also crucial to understand the core domain so as to capture the important business logic which is the gold of the software.
2. Layered Architecture
Now we have a somewhat fixed domain model and ubiquitous language, it is time to think about how to capture these models in software. Eric introduced this layered system which consists of four layers (user interface, application, domain and infrastructure). The domain layer is the heart of business software and should reflect the domain models and ubiquitous language we talked about in part one. Below are the four layers' responsibilities in summary:
- User Interface: interacts with users(human or computer) outside of the software.
- Application: coordinates the domain models in domain layer and delegates tasks to domain layer.
- Domain: reflects the concepts of the business.
- Infrastructure: provides generic technical implementations to support the higher layers, such as persistence.
Below is an simplified example of bank transfer system I took from the book:
As clearly shown in the diagram, each layer only depends on the layer below. What if the lower layer needs feedback from the higher layer? a pattern called OBSERVERS(Gamma et al. 1995) could be used for callbacks.
3. Models in Software
We can now focus on the domain layer and translate the domain models in software. Eric has come up with some building blocks on translating the models and I shall share the basic ones here in Part 1.
1. Model Association
First, I would like to share two snippets of code blocks.
public class Course {
String courseName;
String courseCode;
Map teachers;
...
}
public class Course {
String courseName;
String courseCode;
Set teachers;
...
}
Can you notice the difference between these two classes? They are both modelling the college courses yet why one uses Map and one uses Set? By constraining the association - introducing the term - between the Course and Teacher, the many-to-many relationship now turns into a one-to-many relationship, which is much easier to understand. Courses can be taught by many teachers throughout different terms; however, by constraining a certain term (the key of Map), there should be only one teacher teaching that course.
Bidirectional association is hard to maintain as the application grows more sophisticated. This reminds me of the time when I learnt graph algorithms, DAG( Directed Acyclic Graph) is always easier to handle. So the ultimate goal of this part is to
2. Model Entity and Value Object
Entity is an object that is primarily defined by identity rather than a set of attributes. For example, people are identified by their IDs or fingerprints. When given a set attributes of blood type, height, weight and gender, it is almost impossible to identify which person it is.
Entity is usually assigned an unique ID by computer or by choice and this field should be immutable. Because ID will be used to identify the Entity even when sometimes a new instance is created followed by retrieving it from persistence storage. Since entities are defined by their unique IDs, their attributes are mutable. This implies that entities are stateful. For example, the title of a teacher is changing every few years and that represents the progress of a teacher's career; but, the teacher is still the same person which is uniquely identified by the staffID.
Value object is an object that is defined by its attributes and these are objects that describe things. For example, an office address consists of a faculty name, a building name and room number. This office address object is descriptive. If a teacher has moved to another office address, the original object can be destroyed and a new instance can be constructed. Value objects are in general immutable and are often passed as parameters in messages between objects.
Below are some sample implementation of Entity and Value Object in java:
public interface Identifier {}
public interface ValueObject {}
public interface Entity<ID extends Identifier> {}
public class StaffId implements Identifier {
private final String StaffId;
...
}
public class Teacher implements Entity<StaffId> {
private String Name;
private Office office;
}
public class Office implements ValueObject{
private final String faculty;
private final String roomNumber;
...
@Override
public int hashCode() {...}
@Override
public boolean equals(Object o) {
if (o == this) { return true; }
if (o == null || o.getClass() != getClass()) { return false; }
return faculty.equals(((Office) o).faculty) && roomNumber.equals(((Office) o).roomNumber);
}
}
Another trick here is that try to keep Entity as simple as possible and to move the business logic to Value Object with Side Effect Free Functions. We will talk about this in Part 2.
3. Model Services
When a significant process or transformation in the domain is not a natural responsibility of an Entity and Value object, it can be modelled as Service. Service tends be to named for an activity, rather than an entity - a verb rather than a noun. It emphasises the relationship between objects (Entity or Value Object). For example, before a term starts, a course is registered by many students. During my college time, we have a bidding system where students place their points for a course and students with the higher points than the cutting off point with regards to the quota will secure a slot. Although students have the attribute of bidding points, this bidding action should not be a natural responsibility of a student neither should it be of the course object. Bidding is a verb and it should be modelled as a service.
Stateless service is much easier to test and can ensure the client that any instances created of such service is not depending on its individual history.
4. Conclusion
This is just the part 1 of the DDD series I am planning to write about. Most of the contents are derived from the book. This part only touches the beginning of DDD. Stay tuned for part 2 which we can explore about the life cycle of a domain object (Aggregate, Factory, Repository, etc.) and model less obvious kind of concepts (Specification, Constrains, etc.).