Hi, in this article I try to tell what I have learnt from Eric Evans’ Domain Driven Design concepts. Domain Driven Design, by its nature, covers Model Driven Design, since the basic motivation is to create models based on understood domains.
As a long lived software developer I have involved in many projects from many different businesses. Mainly I have been working with monolith structures and I encountered good layered structures and bad structured projects. Even the latest projects are even dockerized and published in kubernetes environments, even GitOps mechanisms are applied. I would like to say that, even the projects can be monoliths, if layered architecture and modularization is good, that is very good sign that you are applying some understanding that is really the base line of domain driven design concept.
Let’s think through simple examples. For instance, we can think of food ordering application. When you want to order a food, the application initially asks you to select your locaiton/address. Here, we can think of address details implementation and customer relation with this address details. As you can see, all possible address locations in this application readily available and you can easily find your street, avenue, block, apartment, etc. It is not just a simple multiple column based address detail for customer. Even, this address locator module in this food order application can be another API based application, since it is so important in this food delivery business and locating customer addresses especially for logistics. Address concept is a complex model in itself here. However for other business you can attach simple address details even in customer details. For food delivery business, if we used that simple naive approach, there would not be a common address detail for customers and food service companies and millions of millions same and unrelated address details would have been saved and the system would not work after sometime. However for other applications it can be very normal to save simple address detail in a column or couple of columns for a customer. This example gives the intiuation about entites and value objects. For food delivery system, customer and address are conceptualized as entites, however other apps in which the address is handled in a simple manner, here the address is conceptualized as value object. You can save value objects in a denormalized manner or in a different table when necessary for you. If you are utilizing RDMS, you most probably save this simple address detail in a different table.
Entities should be tracked by their unique identites. However, if address details are handled as value objects, there is no need to track them by indentity, they are just some extra info with related entites. Eric Evans states this detail that “VALUE OBJECTS are instantiated to represent elements of the design that we care about only for what they are, not who or which they are”. However, value objects should be represented explicitly as mentioned in DDD principles. Being explicit in concepts, in language, in code implementation is very important. Even though the address details are stored in customer table (it maybe a separate table), it should be represented as an explicit object within Customer entity object. Also it is suggested that, value objects should be mainly immutable. Moreover, as Eric Evans is mentioning that value objects can be shared by different objects regarding performance improvements, when necessary. So being immutable becomes very important in that case. You can also copy the same value object into different instances if it suits in implementation requirements. However there are cases where you can make value objects mutable. As Eric Evans is mentioning:
“If the VALUE changes frequently”,
“If object creation or deletion is expensive”,
“If replacement (rather than modification) will disturb clustering (as discussed in the previous example)”,
“If there is not much sharing of VALUES, or if such sharing is forgone to improve clustering or for some other technical reason”.
Again, I should mention that, in DDD principles, being explicit in everything is very important. Being explicit makes everyone undestand the same thing. In the previous example, if we do not mention address as an object that covers some columns(e.g. street, city, country), and if those attributes are defined directly in customer object, then we can loose the meaning of address concept, and there maybe other values those should be mentioned as value objects. Those separations, groupings of attributes as value objects makes everything very explicit and if we need some restrictions, some policies around those values, value object approach will make us to see the concerns easily. You can find detailed and simple, clear value object example and description from Milan Jovanovic from this link: https://www.milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals
Also, you can check this link: https://domaincentric.net/blog/ddd-building-blocks-in-php-value-object
Actually, if you have been in software development for a long time you have been also thinking about conceptualizing the things and trying to apply good solutions, such as saving data in a normalized way or denormalized way. You may think of templating some part of data in a common place and save this data with it’s relations and use this templated data to visualize the things on UI, such as sold out or not yet sold out seats in a bus or airplane. Since for every same bus model the seat places are same and there is no need to create or add those seats as attributes for bus representation. With bus model, that is used in billing system, you can retrieve the seat plans in another API which is mostly based on bus model that is most probably in different system rather than billing system, maybe this system can be bus management system in travel company, otherwise, if you try to add all seats for each every bus in billing system to manage seat purchasing for every travel, it will create lots of redundant data. If you have those intuations while analysing the things, you are in a good way for DDD understanding.
DDD is also not about structering the systems, code bases, it is also structuring the development teams. Eric Evans gives suggestions from his experiences and importance on team structrures as well. And he is repeatedly mentions that, understanding domain is a gradual thing, in one day or within one month it is hard to grasp all details about domains. Software development is a gradual thing. Communicating with domain experts in different businesses is very curicial. If you do not find proper domain expert, Eric Evans suggests to read related books for related business, such as if you want to develop accounting application you can read books about accounting.
In a classic, legacy approach with layered architecure, we create entities also, however they are pure representation on database that includes only attributes. It is not a good approach to formalize domain concept in coding. When you look at those entities you see only database tables, nothing much. Moreover, to make explicit the domain concept we need to gather related objects, entities, value objects, etc. Where can we gather these objects together? We can focus on a root object-entity. For instance, order entity. This root entity is called Aggregate Root in DDD. In order entity, as you can guess, there are other related entities and value objects, such as order line items, etc. In classic approach we tend to orginize data layer, business layer, etc and by doing so, actually we are loosing domain relations, descriptions. However if we orginize our concepts around aggregate root and gather all related interactions around aggregates, the domain concept becomes more explicit. In legacy approach even we create order line items repos and other separated data layer objects, however in DDD it is prohibited to access deirectly to order line items. All interactions with sub entities or value objects in aggregate root should be done through aggregate root. For instance, if we want to add, update or delete order line item we need interact with aggregate root, that is order entity. By doing so we protect domain integrity, policies or restrictions around aggregate. Noone in future will not attempt to bypass those rules and differentiate the integrity of domains. If it is needed to update some conditions, this approach make us to think carfeully by checking all conditions around aggregate. Eric Evans explains aggregate concept like that:
“First we need an abstraction for encapsulating references within the model. An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each AGGREGATE has a root and a boundary. The boundary defines what is inside the AGGREGATE. The
root is a single, specific ENTITY contained in the AGGREGATE. The root is the only member of the AGGREGATE that outside objects are allowed to hold references to, although objects within the boundary may hold references to each other. ENTITIES other than the root have local identity, but that identity needs to be distinguishable only within the AGGREGATE, because no outside object can ever see it out of the context of the root ENTITY.”
And he mentions some rules based on aggregates:
- The root ENTITY has global identity and is ultimately responsible for checking invariants.
- Root ENTITIES have global identity. ENTITIES inside the boundary have local identity, unique only within the AGGREGATE.
- Nothing outside the AGGREGATE boundary can hold a reference to anything inside, except to the root ENTITY. The root ENTITY can hand references to the internal ENTITIES to other objects,
but those objects can use them only transiently, and they may not hold on to the reference.
The root may hand a copy of a VALUE OBJECT to another object, and it doesn’t matter what happens to it, because it’s just a VALUE and no longer will have any association with the AGGREGATE. - As a corollary to the previous rule, only AGGREGATE roots can be obtained directly with database queries. All other objects must be found by traversal of associations.
- Objects within the AGGREGATE can hold references to other AGGREGATE roots.
- A delete operation must remove everything within the AGGREGATE boundary at once. (With garbage collection, this is easy. Because there are no outside references to anything but the root, delete the root and everything else will be collected.)
- When a change to any object within the AGGREGATE boundary is committed, all invariants of the whole AGGREGATE must be satisfied.
As it is seen that, aggregate root is an entity and can include inner related entities and value objects. It is the source of truth of that aggregate. There maybe lots of interactions, rules between entities in an aggregate. So, you should consider what can be an entity and while constructing relations between entities you can decide which entity should be aggregate root. Since through aggregate root you can gather as many rules, restrictions and interactions, so it is easy to understand the things within this aggregate and in future the consistency within aggregate will not be broken. As a result these aggregates will be the model of that domain, for instance “odering” domain, which includes ordering specific rules, restrictions and other requirements . Even, there can be more than one aggregate root and related entites within a model of that domain. Even, when necessary, you can refer an aggregate root in another aggregate root as an attribute. These entities, aggregates are the initial trials of domain modeling.
Here, it would a good quote from “Domain Driven Design Quickly by Abel Avram & Floyd Marinescu”:
“It is difficult to guarantee the consistency of changes to objects in a model with complex associations. Many times invariants apply to closely related objects, not just discrete ones. Yet cautious locking schemes cause multiple users to interfere pointlessly with each other and make a system unusable.
Therefore, use Aggregates. An Aggregate is a group of associated objects which are considered as one unit with regard to data changes…”
Here, it can be a good time to mention about “Bounded Context”. Actually it is boundary of a context. I believe, again, it would be really good to share this quote from “Domain Driven Design Quickly by Abel Avram & Floyd Marinescu”:
“Each model has a context. When we deal with a single model,
the context is implicit. We do not need to define it. When we
create an application which is supposed to interact with other
software, for example a legacy application, it is clear that the
new application has its own model and context, and they are
separated from the legacy model and its context. They cannot be
combined, mixed, or confused. But when we work on a large
enterprise application, we need to define the context for each
model we create.”
So, here, what does it mean “…we need to define the context for each
model we create.” ? Actually, defining context with outlining the boundaries is set of documentations before implementation or just before implementation. Especially, context map concept is mentioned in DDD. And again a great quote from “Domain Driven Design Quickly by Abel Avram & Floyd Marinescu”:
“While every team works on its model, it is good for everyone to have an idea of the overall picture. A Context Map is a document which outlines the different Bounded Contexts and the relationships between them. A Context Map can be a diagram like the one below, or it can be any written document. The level of detail may vary. What it is important is that everyone working on the project shares and understands it.”
Eric Evans also mentions about “Domain Vision Statement” concept. He is saying that, “Write a short description (about one page) of the CORE DOMAIN and the value it will bring, the “value proposition.” Ignore those aspects that do not distinguish this domain model from others. Show how the domain model serves and balances diverse interests. Keep it narrow. Write this statement early and revise it as you gain new insight.”
All these entities, some of them will be aggregate root, that we will form aggregates within these contexts. There will be interactions between different contexts and there are a lot of ideas about how those interactions should be handeled in DDD descriptions. For instance, “Shared Kernel”, “Customer-Supplier”, “Separate Ways”, “Open Host Services”, “Anti Corruption Layer” approaches. Actually those approaches are applied when they are necessary, they are some kind of system design approaches. You can find detailed descriptions, scenarios from Eric Evans’ book under this chapter: “Relationships Between BOUNDED CONTEXTS”.
Eric Evans also mentions about “Core Domain” detail. For example, if the project focus is e-commerce platform, ordering, billing and other domains can be core domains. However, reporting, organizational chart demonstrations may not be a core domain. As you understand, all software applications are elements of those domains, such as organization charts application or component. He also mentions a lot of variaties with cons and pros about how to handle those domains which are not one of your core domain, such as outsourcing, using 3rd party applications, etc. Those are mainly described as “Generic Subdomains”. Also he suggests that, the core model or core models are important to be hanleded/implemented by company’s developers, since, if you take 3rd party tools for core domain, in future for some extra requirements you may not progress by yourself and you may not produce specialized components for your needs.
He suggests to create also a document to highlight core domain and the document name is referred as “The Distillation Document”. This document is suggested to be 3 or 7 pages long. He mentions also, “Write the document to be understood by the nontechnical members of the team. Use it as a shared view that delineates what every-one needs to know, and a guide by which all team members may start their exploration of the model and code.” Also he mentions the importance of this document:
“If the distillation document outlines the essentials of the CORE DOMAIN, then it serves as a practical indicator of the significance of a model change. When a model or code change affects the distillation document, it requires consultation with other team members. When the change is made, it requires immediate notification of all team members, and the dissemination of a new version of the document. Changes outside the CORE or to details not included in the distillation document can be integrated without consultation or notification and will be encountered by other members in the course of their work. Then the developers have the full autonomy that XP suggests.”
He gives a lot of examples and ideas about brief but also clear, powerful documents. Even he encountered in a project that, he was given two hundered pages long documentation for a project and it was very frustrating for him. He tried to find out core domain details within this document. He got help from business analyst who knows the domain well.
Actually, if you have involved in analysing the requirements in projects you most probably have done similar things, such as summary documentation to describe the domain, initially to yourself. You have most probably tried to segregate different contexts and try to figure out some necessary relations between contexts. DDD principles and concepts give you more broader strategies, ideas about how to approach for some scenarios. DDD principles includes all aspects of DevOps progress, team setup ideas, outsourcing options, etc. However it gives more important logic of domain and modelling concepts, such as aggregate boundaries and encapsulation related entities and objects around aggregare roots.
There is also one more importing thing in DDD I should mention is that domain services. In legacy approach, in many projects, as mentioned above, we create entites as representation of database tables without any business logic then we create other layers to include infrastructure and business logic details, such as business logic services and database related layers, dto mapping layers, etc. DDD mentions that, this approach is not a good approach, especially for complex projects which can have many integrations. Since, as you can recall, in DDD, the contexts in domain are encapsulated by aggregate roots and all access to the contexts are handled by aggregate root. This approach protects the the consistency within an aggregate and it also protects the consistency and explicity of domain. Since, by doing so, you are also forced to structure you programming folders, packages according to the aggregate boundaries. In many ways, at first look, everything is more explicit and the coding is more self descriptive ever. In DDD, domain services are created when necessary, since all context related interactions are living in aggregate roots, however, sometimes, you may need interactions between contexts or even domains and you may not find a visualise the things within an aggregate, you can create domain services. It is really different approach than legacy implementations. Again, here is a good quota from Eric Evans:
“…For example, if the banking application can convert and export our transactions into a spreadsheet file for us to analyze, that export is an application SERVICE. There is no meaning of “file formats” in the domain of banking, and there are no business rules involved. On the other hand, a feature that can transfer funds from one account to another is a domain SERVICE because it embeds significant business rules (crediting and debiting the appropriate accounts, for example) and because a “funds transfer” is a meaningful banking term. In this case, the SERVICE does not do much on its own; it would ask the two Account objects to do most of the work. But to put the “transfer” operation on the Account object would be awkward, because the operation involves two accounts and some global rules.”
Actually, in legacy implementations we tend to create utilities instead of calling them as services, actually they are services, such as in this quote, exporting spreadsheet.
Actually, for domain services, Eric Evans initially states that: “In some cases, the clearest and most pragmatic design includes operations that do not conceptually belong to any object. Rather than force the issue, we can follow the natural contours of the problem space and include SERVICES explicitly in the model.”
Some other related quotes from Eric Evans:
“A SERVICE is an operation offered as an interface that stands alone in the model, without encapsulating state, as ENTITIES and VALUE OBJECTS do. SERVICES are a common pattern in technical frameworks, but they can also apply in the domain layer.
…
The name service emphasizes the relationship with other objects. Unlike ENTITIES and VALUE OBJECTS, it is defined purely in terms of what it can do for a client. A SERVICE tends to be named for an activity, rather than an entity — a verb rather than a noun. A SERVICE can still have an abstract, intentional definition…
SERVICES should be used judiciously and not allowed to strip the ENTITIES and VALUE OBJECTS of all their behavior. But when an operation is actually an important domain concept, a SERVICE forms a natural part of a MODEL-DRIVEN DESIGN. Declared in the model as a SERVICE, rather than as a phony object that doesn’t actually represent anything, the standalone operation will not mislead anyone. A good SERVICE has three characteristics.
1. The operation relates to a domain concept that is not a natural part of an ENTITY or VALUE OBJECT.
2. The interface is defined in terms of other elements of the domain model. 3. The operation is stateless.
…”
Before conclusion, I should explicitely mention about Ubiquotus Language. Previosly I mentioned a lot about being explicit in everything related with a domain. Ubiquotus Language is the key that makes the whole understading clear. Related keywords and descriptions even are used in implementation and discussing the things related with domain by constructing Ubiquotus Language makes everyone’s understanding snychronized. Everyone understands the same thing around ubiqutous language. So, especially for big projects which can have many integrations, people understand each other very clear and many concepts can be captured easily. So, ubiquotus language surrounds every detail including in implementation definitions.
In conclusion, aggregate boundaries within contexts and implementation approaches in DDD makes many things more explicit and even by looking at implementation many concepts can be understood easily. It protects the domain idea in a powerful manner that in future you can adapt your continues integrations without many conceptual ambiguities. There are so many other details in DDD details, such as factories, modules, etc. I tried to emphasize the domain context and related aggregate concept in this article.
Thanks for reading.
Mustafa Cem Yaşar