I have seen several experienced software developers who apply some of the concepts described in Domain-Driven Design which, to some extent, is good, however those developers only apply what is called DDD-lite. They create Entities (but they do not distinguish between root aggregates or normal entities), they create repositories (but they do not use them to obtain only the root aggregates instead they make them to retrieve any entity) or sometimes I have even seen some Value Objects (but they do not guarantee immutability). Applying DDD-lite and not full DDD is not what we should aim for because we could easily end up building a well known anti-pattern: Anemic Domain Model;
Just to refresh your memory, an anemic Domain Model is when your domain model is weak because it lacks methods (behaviour) or the behaviour is spread everywhere but not in the domain classes.
Remember: one of the main benefits of Object Oriented Programming paradigm is putting together data (fields) and behaviour (methods).
I will proceed to enumerate and explain what it is known as the building blocks of DDD.
I guess the first thing to do is to perform a 10,000 foot view of these patterns. Let’s use a diagram for this purpose.
We will divide these patterns in three categories; in the first category we will put the patterns that help us to express our model, these patterns are Entities, Services and Value Objects. In the second category we will group the patterns that help us with the life cycle of the domain objects: we will see Aggregates, Repositories and Factories. Finally, the last but not least category will help us to reduce the coupling between elements in our application: layered architecture, modules and domain events belongs to this category.
01. Expressing the domain model
Just after the first conversation with the domain experts, or even before (for instance during the kick-off meeting of a project), a lot of names and verbs start appearing. Our object-oriented wired brain begins identifying some classes, which correspond to the names, and actions that we can do with these classes (the actions resemble the verbs). Let’s see an example:
The recruiter will send an invitation to a candidate in order to record a video.
Names: recruiter, invitation, candidate and video.
Verbs: send and to record
Quickly a developer’s mind starts thinking: I am going to need a Recruiter class and a Candidate class: both recruiters and candidates are people, so maybe I will need a Person class so Recruiter and Candidate can inherit from it. The Recruiter class will have a method call SendInvitation that will need a Candidate as a parameter…
Some of these classes will have two properties that will make them special: continuity and identity. We will called them Entities. Other classes, instead, will be described by their attributes and we will call them Value Objects. This separation won’t be always clear and most of the time we will have a bias to create Entities. Let’s try to explain these two patterns and see if we can put a bit of light in this darkness.
We said that Entities have identity and continuity. What does it mean? Let’s imagine I want to create software to invoice my clients. So at some point in my application I will have to register a client, at some point I will want to create an invoice for my client, so I will have to retrieve the entity that represents my client; maybe for his birthday I want to send him a birthday card, again I will have to retrieve it.
We can see that a client is something that has some kind of lifecycle or continuity, we create them and we will retrieve them several times until one day we decide that we are not going to use the entity “client” anymore and we will archive it. On the other hand, a client is a person, and a person has attributes, for instance, a name, a surname, a date of birth, an email, an address, and so on. A person can change so much during their lifecycle, for example we can change their surname, in some countries when a women gets married she takes the surname of her husband, doing that it does not change the identity of that person, she is still the same person from when she is born until she dies (in our software from when we register the “client” until we decide to archive the “client” for whatever reason). This sense of identity and continuity is what defines an entity.
How do we define an identity for our Entities? Well, there are different options to define an identity, and each of them has pros and cons; that could be material for another blog post at some point. For the time being let’s just list some of this options: using a universally unique identifier, using a numeric sequence, creating some kind of codification that makes the identity unique, etc. But there is something that we must guarantee, regardless of the option we choose, the immutability of the identity. An identity cannot change; we have to guarantee identity stability, if not we can end up with data corruption.
Some objects are not defined primarily by their attributes. They represent a thread of identity that runs through time and often across distinct representations. Sometimes such an object must be matched with another object even though attributes differ. An object must be distinguished from other objects even though they might have the same attributes. Mistaken identity can lead to data corruption.
There are other objects that do not have an identity, nor a continuity; instead the only thing that we should care about these objects are their attributes. These object are created, used and finally destroyed, we should try to make them immutable, so they cannot change once they are created. They are used to describe, measure or quantify things. Changing an attribute of these objects does not make any sense, because changing an attribute would result a completely different thing, this is why we should strive to make them immutable. In fact, the attributes of these objects act as whole. I like Vernon’s description for this concept.
A Value Object may posses just one, a few, or a number of individual attributes, each of which is related to the others. Each attribute contributes an important part to a whole that collectively the attributes describe. Taken apart from the others, each of the attributes fails to provide a cohesive meaning. Only together do all the attributes form the complete intended measure or description.
Could you think about an object with those characteristics? Sometimes it is difficult because of our bias of thinking about Entities. What about the address of a client? Let’s imagine an address composed by a house number, a street name, a post code and a country. What happens if we change the street name? Are we talking about the same address? No, right? It is a completely new address. This address does not have an identity, it is just a few attributes that makes sense together as a whole and if we take one the attributes away we cannot describe the address properly.
Two value objects will be equal if and only if all their attributes are equal. For instance, in our example, we would say that two addresses are the same if and only if they have the same house number, the same street name, the same post code and the same country.
Due to some of the characteristics of Value Objects, like the lack of identity or its immutability, we should strive to model using Value Objects instead of Entities if possible. We would end up with less complex systems, easier to maintain and we could improve their performance.
Someone could think that with Entities and Value Objects we could model our domain. In fact, Entities and Value Objects are the main elements. However, once we start delving into our domain we will notice that there are operations or actions, sometimes involving multiple objects, that do not naturally belong to these objects, and trying to add them to the objects we could end up making the objects lose their clarity and making them more difficult to understand. Services to the rescue!
Let’s imagine for a moment that we are going to build an application for a bank. During our first conversation with the domain experts we have discovered a few Entities: Customer and Bank Account and even a Value Object: Money which is composed by two attributes – Amount and Currency ($500, €80 or £700). That’s going well. But then one of our domain experts says:
We have to be able to transfer money from one account to another account.
Oh oh… Where should we put this operation? Maybe we could add the operation into the Bank Account entity, and it would be the origin account that starts the operation; for this operation it would receive a destination account and the amount to transfer, so it would be something like that:
var money = new Money(500, Currency.Dollar); originAccount.Transfer(destinationAccount, money);
However, doing it in this way, the origin account should withdraw the amount from itself and perform a deposit on the destination account. I don’t know about you guys, but for me this operation does not fit well here. Adding money on a destination account is not a responsibility from an origin account. So, how should we do it?
We could create a stateless class called TransferService, with an operation Transfer that receives two accounts, origin and destination, and the money to transfer.
Some concepts from the domain aren’t natural to model as objects. Forcing the required domain functionality to be the responsibility of an ENTITY or VALUE either distorts the definition of a model-based object or adds meaningless artificial objects.
Services are so useful to express operations that do not belong naturally to an Entity or Value Object, however we have to be careful and not to try to create services too soon without trying to first add the behaviour into the appropriate object, this could lead us to an anemic domain.
Just like the name suggests, a service is an operation, they are defined just in terms of what they offer, what they can do for a client or multiple clients. Good services have three characteristics:
- Their operations do not fit naturally in an Entity or Value Object
- The interface involves other elements from the domain model
- They are stateless
We will find multiple services in other layers of the application: application layer or infrastructure layer. However, the service that we are talking about are Domain Service, so they must express domain concepts.
That’s it for now folks! We will continue with more patterns soon…
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