The 2nd part of chapter 5 in Domain-Driven Design by Eric Evans talks about modules (a.k.a. packages) and how to correctly divide the objects into modules to serve the purpose of the domain model and support the communication between the developers.
The first rule of creating good modules, is that modules should have low coupling between each others and each module should be highly cohesive in itself.
Low coupling means that each module should be independent of other modules in the system. When that's achieved; it gets easier to understand the model since you're dealing with a small part of the system at a time without the need of thinking about the whole system (objects outside the module).
High cohesion between the objects of the systems which have related responsibilities means that the components of the module are all related thus making it easier to understand what the module does as a self-contained subsystem as each module should focus on one idea/responsibility.
The meaning of the objects in the domain should drive the choice of the module and keep in mind that putting some classes together in a module tells the other developers that they should think of these classes together.
If the model is telling the story of the system then the modules are the chapters and they should be named in ways that convey their meanings. The names should be part of the Ubiquitous language (the common language between the technical team and the domain experts)
So modules should:
- Tell the story of the system
- Contain cohesive set of concepts
- Have low coupling with other modules
- Have meaningful names that become part of the Ubiquitous language
You might need to choose between technical cohesion and conceptual cohesion. In that case, choose conceptual cohesion because the developers can handle the results of low technical cohesion if they correctly understand the story of the model.
Modules will need to be refactored as the system grows, however, since modules are large; refactoring them is disruptive and harder than refactoring model objects. That leads to modules reflecting a much older version of the model.
Initially, modules are highly coupled because the model changes as the project goes on. Lack of refactoring keeps that inertia going. That's why modules should be easy to refactor and easy to communicate to other developers what modules do.
Some frameworks divide entities into modules based on their technical responsibilities (example: separating entity's business logic from data access) but this makes modules harder to understand and therefore increases the cost of development and refactoring. The justification given for doing that is that the modules might be deployed on different machines but most of the time that doesn't happen and doing that is costly because:
- The code no longer reveals the model
- The developers no longer understand the model or reason about it as meaningful pieces.
Minimize the technical partitioning restrictions and apply only the essential ones. The most important purpose of the module is to separate the domain layer from other code.
This part in the book talks about object oriented languages and how they are the most fitting for the domain model. That's because at the time of writing this book, the usage of object oriented languages was on the rise and that's why it was considered to be the best fit.
However, currently many paradigms are being adopted and the book describes how to mix paradigms as well. That's why instead of talking about object-oriented paradigm I will try to generalize the rules of mixing the paradigms because I think it's more relevant for the current trends in software engineering.
Note that domain-driven design highly relies on OOP and most of the techniques are specific for OOP but I will try to generalize the concepts when possible. But that is my personal opinion and it's important to know that as per the book, Object-oriented languages are the best fit for applying Domain-Driven Design.
- Don't fight the dominating paradigm: first try to model the domain object in the paradigm you're already using. You can often rethink the model and look at it from another perspective so try to do that before using a new paradigm.
- Lean on the ubiquitous language: If you're using a new paradigm; stick to the ubiquitous language. That way the model can still be reasoned about.
- Don't be limited by the modeling tools: if you're using a modeling tool like UML don't distort the model into what's easier to represent with the tool. Use tools that fit the paradigm.
- Be skeptical: Is the paradigm you're introducing really worth its weight or can the object be represented with the current paradigm? Sometimes objects are not obvious to model but they can often be modeled to different paradigms.
The concept of modules is not new but it is important to have a process for creating them. This part is all about the rules of creating those modules.
I researched if DDD should mainly use object-oriented languages and I didn't find that to be the case and that's why I tried to not focus on it. It's important to know that the book is almost 20 years old and some parts are not very relevant. Also, I believe DDD is more about communicating the model than writing code.