It’s been a while since i wrote the first part of DDD series. Within this second part, i will continue with one of the most important thing of DDD before you’re going further with the implementation. First thing first, you must know about DDD building blocks as you can see below:
- Value Objects
- Aggregate Roots
Tighten your “seatbelt” :-). We’re going through the details of those aforementioned blocks now.
Entities (or “entity” in singular form) is a plain object that has an identity (ID) and potentially mutable. Each entity is uniquely identified by an ID rather than by an attribute, therefore two entities can be considered as equal (identifier equality) if both of them have the same ID, even though they have different attributes. This means the state of the entity can be changed anytime, but as long as two entities have the same ID, both are considered as equal regardless what attributes they have.
2. Value Objects
Value Objects are immutable. They have no identity (ID) like we found in Entity.
Two value objects can be considered as equal if both of them have the same type and the same attributes (applied to all of its attributes).
They are often uses for a thing like message passing and in fact they are particularly useful in the API layer within an Onion Architecture (going to talk about this Onion Architecture later) to expose your domain concepts without necessarily expose the immutable aspect.
3. Aggregate Roots
Aggregate root is an entity that binds together with other entities. Moreover, aggregate root is actually a part of aggregate (collection/cluster of associated objects that treated as a single unit for the purpose of data changes). Thus, each aggregate actually consists of an aggregate root and a boundary. For example, the relationship between Order and OrderLineItem within SalesOrder domain can be considered as an aggregate where Order acts as the aggregate Root, while the OrderLineItem is the child of Order within SalesOrder boundary.
One of the key features of an aggregate root is the external objects are not allowed to holds a reference to an aggregate root child entities. Thus if you need access to one of the aggregate root child entities (a.k.a aggregate), then you must go through the aggregate root (i.e. you can’t access the child directly).
The other thing is all operations in general within the domain should where possible go through an aggregate root. Factories, Repositories, and Services are some exceptions to this, but whenever possible, if you can create or require that an operation go through the aggregate root, that’s going to be better.
Repositories are mostly used to deal with the storage. They are actually one of the most important concepts on the DDD because they have abstracted away a lot of the storage concerns (i.e. some form / mechanism of storage).
The repository implementation could be a file-based storage, or database (SQL / NoSQL based), or any other thing that is related with storage mechanism such as caching. Any combination of those are also possible.
Repository should not be confused with the data store. A repository job is to store aggregate roots. Underneath that, repositories implementation may actually have to talk to multiple different storage locations in order to construct the aggregates. Thus, a single aggregate root might be drawn from a REST API, as well as a database or files. You may wrap those in something called the data store, but the repository is sort of a further layer of an abstraction on top of all those individual data stores. Usually, i implement the repository as an interface within the domain/domain services layer within Onion Architecture (skip for now, gonna talk about this “Onion” later), and then the implementation logic of the repository interface is going to be defined in the infrastructure layer.
The factories are used to give an abstraction to the object construction (see Factory design pattern from GOF).
A factory can also potentially return an aggregate root or an entity or perhaps a value object.
Often times, when you need a factory method for an aggregate root, it will be rolled in to the repository. So your repository might have a finder create method on it.
Usually factory also implemented as an interface within the domain/domainservices layer with the implementation logic defined in the infrastructurelayer.
A service is basically exist to provide a home for operations that don’t quite fit into an aggregate root. As an example, when you have an operation that you don’t know on which aggregate root it goes into, perhaps it operates on multiple aggregate roots or maybe it doesn’t belong to any existing aggregate root, then you can put the logic into a service. However, don’t be rush to put everything into a service. First and foremost, its better to carefully analyze whether the operation fits into one of the existing aggregate root or not. If you couldn’t find the aggregate root, subsequently better to ask your self in case you have missed one aggregate root or perhaps there are domain concepts that you haven’t considered that should be brought into your domain before you put the operation into a service.
Other Important Things
I often found that many developers use term VO (Value Objects) and DTO (Data Transfer Object) interchangeably. They consider both are just the same. This is quite annoying for me. In that case, i’d like to clarify here that both are refers to the different things.
As depicted on the picture below, VO and DTO are subset of a POJO (Plain Old Java Object) / POCO (Plain Old CommonRuntime Object). Entity is also a subset of POJO/POCO.
In the above depiction, POJO and POCO can be interchangeably used. Both are refer to the similar thing. Both are just domain object that mostly represent the domain / business object within the business application.
The term POJO (Plain Old Java Object) was coined by Martin Fowler and very popular in Java community, while POCO (Plain Old CLR Object / Plain Old Class Object) is widely used in the dotNet.
As mentioned earlier, DTO, VO, and Entity are just a subset of POJO/POCO. However they are really different things as described below:
DTO is merely a stupid data container (only holds data without any logic). Thus, its anemic in general (only contains attributes and getter/setter). DTO is absolutely immutable. Usually we use DTO to transfer the object between layers and tiers in one single application or between application to application, or JVM to JVM (mostly useful between networks to reduce multiple network call).
VO is also immutable, but what make it different with DTO is that VO also contains logic.
That’s all for now, see you on the next part :-).