A model is expressed in code by several patterns of model elements:
- Value objects
Value objects are attributes that describe the state of something else. They have no conceptual identity, they describe a characteristic of another object. They are properties of other objects.
Value objects are different than entities in how they need to be tracked. If only the value is relevant, we are in presence of a value object. If it is also relevant what value we are referring to, then we are in presence of an entity.
Entities are objects that represent something relevant in the domain model. They exist on their own right, they can not be swapped by another object with the same data, they are crucial in the domain logic.
The example provided in the book at hand is the seats in a stadium. If our domain says that each ticket has a specific seat assigned to it, then we need to track this seat throughout the application and it has a specific identity: it is and entity. However, if there is no connection between a ticket and a seat, a seat is just a generic thing, it can be any seat, it can be swapped, we don’t need to track it, it does not have a specific identity: it is not an entity. However, it can probably be a value object, with colour, size, weight, etc.
Technically, an entity implementation should be very slim, contain only the properties that define it and the logic specific to those properties. An entity must not have logic that handles objects they do not own.
An entity must have a way to uniquely identify it. This can be done using a set of properties, but more commonly, an artificial property called “id” will be used to hold a unique number or string.
An association between objects can be compared at a DB level as the connections between two tables that represent objects. It can e seen at OOP level as an object that contains another object, or collection of objects, as one of its properties.
A pragmatic rule, to maintain code simple and maintainable, is to not create nor keep dead code (code that is not used). Associations are reflected, in the implementation of the domain, by code. Therefore it makes total sense to keep associations to the minimum required by the domain. This means that although a bidirectional association might make sense in a generic context, if in our specific domain it only makes sense as a unidirectional association, we must not waste time and resources to create unnecessary code. It does not reflect the model and increases complexity and maintainability costs. Basically it bloats the application.
Associations can be reduced in three ways:
- Imposing a unidirectional association
- For example: We can say that a country had many presidents, and that a president was president of a specific country. This reflects a bidirectional association, however if in our specific domain we always think in terms of “this country had this set of presidents” and never in terms of “this person was president of that country”, we must not create the code to support the second direction, therefore imposing a unidirectional association;
- Adding a qualifier (filter)
- For example: If in the previous unidirectional association we realise that in our specific domain it only makes sense to query about a country president by providing a date, and knowing that at a specific date there is only one president, then we can reduce the association from unidirectional 1-n to unidirectional 1-1. Thus again creating a beter reflection of the domain and simplifying the implementation;
- Eliminating non essential associations
- If an association is of no use in our specific domain at hand, it must be completely removed;
Services are objects that represent a process, as opposed to an anonymous value or a specific thing with its own identity, data and functionality related to that data.
- Represents a process, a flow of actions on one, or several, entities;
- Is stateless, meaning it does not hold a state that alters its behaviour. Therefore it could, and should, be a singleton;
Modules & Packages
Modules should also emerge as a meaningful part of the domain model. They group a set of functionality, a concept, and isolate its complexity from the remaining the domain model modules and implementations.
As in individual objects, its important that there is low coupling between modules and high cohesion within them.
Just as smaller code units evolve as the domain gets better understood, the same happens with modules who should, as classes, be re-factored and reorganized to better reflect the domain model and reduce technical complexity.
If we end up with high coupling between modules, we need to rethink the domain model, to clarify it, to disentangle it, and perhaps discover other concepts that might be new or have been overlooked.
Choose modules that tell the story of the system and contain a cohesive set of concepts. (p.110)
When building an application, the technical implementation should be a reflection of the domain model. When implementing a domain model, the technical complexity should be second to conceptual clarity.
Developers can handle [ … technical complexity … ] if they understand the story the model is telling them. (p.111)
A code smell for detecting a wrong module design, is when we have a class in one module that depends on a class in another module, but the modules don’t seem to have a conceptual dependency. In such cases, maybe we need to move one of the classes, or rethink the actual modules.
Having several high level packages for one conceptual module, ie scattering a concept through several high level packages, can lead to increased difficulty in re-factoring modules.
An argument in favour of such code module/concept scattering is its deployment in different servers. However, this is not usually the case. This structure is usually implemented just in case its needed, and this adulteration of the domain model turns out to be too much of a sacrifice.
Unless there is real intention to distribute code in different servers, keep all the code that implements a single conceptual object in the same module, if not the same object. (p. 115)
We should use modules to encapsulate domain concepts, and packages inside this modules to encapsulate architectural layers (application, domain, infrastructure, …).