In the previous post we introduced some of the DDD building blocks. We divided those building blocks in three categories, in the first category we put the patterns that help us to express our domain; in the second category we are going to place the patterns that help us to manage the life cycle of the domain objects; finally, in the last category we will detail patterns that help us to reduce the coupling between elements in our application. This post is about the second category.
02. Managing the life cycle
A domain object, like so many things in our surroundings, has a life cycle. They are born and they die, as sad as it sounds. Some objects will experience a short life whereas others will last for a long time. Let’s add an example of an object with a short life and an object with a long life: let’s imagine that I need an object to calculate the payroll of an employee, and we will call this object PayrollCalculator. At the end of the month I will need to calculate the payroll of my employees, so I will create a PayrollCalculator object, I will pass an employee to the PayrollCalculator and it will give me an object representing the money that I have to pay to my employee. Once this operation is performed, I won’t need the PayrollCalculator any more so I will kill it or the garbage collector will deal with it. On the other hand, the domain object that represents the employee will have a longer life cycle; we will need this object while the contract between the person and the company is valid, this could be hours, days, months or years. Obviously, we cannot keep this object in memory, unless that we guarantee that the memory is not volatile, so we will have to persist the domain object in some kind of more permanent support: a database, a file in a file system, etc.
Another challenge that we will have to face, sooner or later, is to maintain the data integrity (to make sure that the invariants – or consistency rules – are valid during data changes) of the domain object through their life cycle. We will see three patterns that will help us to deal with all of these challenges related with the life cycle of the domain objects: Aggregates, Repositories and Factories.
An AGGREGATE is a cluster of associated objects (Entities and Value Objects) that will help us to encapsulate relationships within the domain and it will be treated as a unit for the purpose of data changes; An AGGREGATE exposes a root entity – the only element of the AGGREGATE that outside objects are allowed to hold references to – and a boundary that defines what is inside the AGGREGATE. Oh my Lord… what does it mean?
Let’s imagine that we have built software for a library. To register the books that the library has we have built a model of a book. A book is composed by multiple pages and our system contains a digital copy of each of them. Our book is an ENTITY and it has an Identity that makes it unique in our system. We would want to track how many times a page has been scanned. So, a page would be another ENTITY; however, its Identity doesn’t have to be unique in the system, because we are not going to ask for a page without asking first for the book that this page belongs to. A page of a book does not make sense outside of the context of the book. Hence, a book is the root ENTITY of an AGGREGATE that defines a boundary around the book entity and the page entity.
Characteristics of an AGGREGATE:
- Root ENTITIES have global identity. ENTITIES inside the boundary do not need global identity only local identity.
- Objects outside the AGGREGATE cannot hold a reference to objects inside an AGGREGATE, except the Root ENTITY.
- Only Root ENTITIES can be obtained directly with database queries.
- Objects within the AGGREGATE can hold references to other AGGREGATE Root ENTITIES.
- A delete operation must remove everything within the AGGREGATE boundary at once.
- When a change to any object within the AGGREGATE boundary is committed, all invariants of the whole AGGREGATE must be satisfied.
Can you think in terms of AGGREGATES? What about a Post and Comments? Outside of the context of a Post (outside of the boundary of the AGGREGATE), does it makes sense to talk about a Comment (to hold a reference to a Comment)? The answer is no. In order to have access a comment we should always traverse a Post, so first we would query the database to obtain a Post and then access the Comments of a Post.
Another example, let’s imagine an AGGREGATE composed by an Invoice and their Invoice Lines; every Invoice Line has an association with a Product, which is another AGGREGATE, the direction of the association is from the Invoice Line to the Product and not in the other way, it would violate the encapsulation of the AGGREGATE. Let’s imagine that we have an invariant that says something like the total amount of the Invoice cannot excess $10,000. Could we guarantee this invariant if we allow the access to the internal ENTITIES, like the Invoice Line? We could, however it would not be an easy task. If we want to modify an Invoice Line or add a new Invoice Line, we should always do it through the Root Entity, in this case, the Invoice.
Some times the creation of a domain object is a complex task that requires several steps. We could add all of these steps inside the constructor of the object, however that could be wrong for several reasons; the first one is that we do not do this in the real world. Could you imagine a calculator building itself? Or an airplane?
We could easily put the responsibility of the creation of the object into the client that is going to use the object. However, doing that the client must know so much about the internal parts of the object and its invariants. In addition, we could spread the knowledge, about the object creation, through multiple parts of our application and increase the temporal coupling.
Another problem about object construction happens when we have several ways of building an object. We could solve it overriding the constructor (having several constructors with different parameters). However, a client that needs an object won’t know which constructor to use.
To solve all of these problems, Evans suggest the use of factories. A factory won’t have any other responsibility apart from encapsulating the object creation. These factories are part of the domain. We could create these factories using the patterns provided by the book Design Patterns: Elements of Reusable Object-Oriented Software, like Factory Method, Abstract Factory or Builder.
We have talked about objects’ life cycles; we said that sometimes the life cycle of an object could be quite long: hours, days, months, and so on. At some point we will need to persist those objects that are not transient. If we persist them we will need to retrieve them later at some point. Therefore we are going to need some kind of mechanism to persist objects and retrieve objects that are persisted. Welcome REPOSITORIES!
REPOSITORIES will help to remove the infrastructure code needed to retrieve objects from the client that is going to use the domain object. In addition, it will help us to avoid clients pulling the exact data they need from the database without using the associations established in the AGGREGATES and breaking the expressiveness of the Domain Model.
We need REPOSITORIES to access to a subset of persisted object, the ROOT AGGREGATES, through a search based on attributes of those objects. The rest of the objects can be accessed through traversal association from the ROOT AGGREGATES. Sometimes it is possible to access some VALUE OBJECTS globally, but because it is not quite common I would just ignore that and just think about ROOT AGGREGATES.
A REPOSITORY will emulate a collection of objects of a certain type. This collection we will have methods to add objects and delete objects as well as to retrieve them. If we look behind the scene of a REPOSITORY, it will have all the mechanisms to query a database (or multiple databases), do the inserts and deletes.
A client will use query methods exposed by the repository to obtain the objects they need. The REPOSITORY will encapsulate the data access layer. It can expose other methods like methods to summarise information such as count or sum.
- Simple model for obtaining persistent objects and managing their life cycle (Insert and Delete)
- Decouple domain design from persistence technology
- Restrict which object can be accessed globally (ROOT AGGREGATES)
- Improve testing (we can swap the implementation of the real repositories with dummy objects)
The implementation of our REPOSITORIES can vary a lot depending of the technology that we chose. Usually we will use some kind of framework ORM (Object-Relational Mapping) to implement our repositories: Hibernate, Entity Framework, Dapper, etc.
“Grasshopper”, use FACTORIES to deal with the beginning of the life cycle of a domain object and REPOSITORIES for the rest including its death.
About The Author
I’m extremely passionate about knowledge; wherever I go, you will always find a book inside my trusty rucksack - it could be a technical book, a novel, essays; anything that broadens my mind and understanding.
- 18th April, 2018
- 18th April, 2018
- 8th March, 2018
- 31st January, 2018
- 30th January, 2018