In January 2020 PHPSP asked me to speak about DDD at a meetup, but because it is a topic that I have already lectured on a few times, I was thinking of a different approach to bring. In the article Demystifying the DDD I talk about the principles of Domain Driven Design, in the article Distilling a domain with DDD I talk about domain distillation and building blocks using a fictional case for teaching purposes, and in the article DDD and Microservices - From business to architecture I talk about how these principles can be applied to architectural decisions, like microservices. What I realized was missing was to show a real scenario, a success story, so that people would have a concrete example of how DDD can bring positive results for the product and for the team, when well applied.
I consulted some colleagues. I spoke to Leonardo Prange, software engineer at Qualyteam, who spoke on the topic on the .NET Architecture trail at TDC São Paulo in 2019, and of course, with the Elemar Jr., who has extensive experience in the subject. I started talking to Leonardo, and I was very happy and surprised when he mentioned that, at a certain point in the project, Qualyteam hired Elemar's consultancy to guide them. I realized that I would have both views of the same case - that of the team and that of the consultancy, and that it would be more interesting than I imagined. I hope this article adds as much for those who read it as it added for me to write it! Come on.
In 2018, Qualyteam, a company that provides quality management platforms, understood that its solutions were not prepared for the international market. The monolithic system was difficult to expand, and the team was considering starting a process of rebuilding legacy software to make it more general, but chose to build a more specific and lean solution, aiming at a scalable self-service business model.
The solution was born with a monolithic multi-layered structure separated by business modules, however the team often faced accidental technical complexity. They believed that if they used a microservice architecture based on DDD principles, they would have more clarity in the domain and more speed when deploying new features. They made the decision to hire the consultancy of Elemar Jr, an architect known for his knowledge of distributed systems and highly complex solutions, to guide them in this transition process.
The consultancy lasted 2 days. In Elemar's view, the concept of ubiquitous language was not being worked on. He defended prototyping as a means for domain experts (key users and business analysts) and technicians (developers and architects) to communicate with the same language. It’s what the user can best visualize - the screens and navigations of the product he will receive, instead of classes and diagrams that, for him, they can be abstract. Even during this prototyping process, it is important to guide users to think about actions rather than the data itself. Without this criterion, it is common for prototypes and modeling to start from the registers, and in reality they are a support for the operations that must be carried out in the system. He also defended the team to give up concepts that were not necessary. Any technical decision that the team was unable to justify should be reviewed. Bearing in mind that saying that something "is a market trend" is NOT a justification. 🙂
Over time, the Qualyteam team took a technical and strategic course that culminated in more cohesive, testable systems, and a better team perception of development. We will highlight the most relevant decisions:
Was it necessary to rewrite the legacy?
The team understood that it was not. It was a system that paid the bills, and served a niche market well. They decided to dedicate their efforts in the field that was not yet served, without adding a concern to make him generalist. Serving a specific domain, it is simpler to identify the ubiquitous language and avoid accidental complexity.
Would using microservices improve anything?
No. The team realized that they were considering adopting this architecture because it is a market trend, and not because it would solve a problem. On the contrary, the problems they faced with the monolith would be multiplied by the number of services distributed. They decided to build monolithic systems that served a domain and its subdomains.
Was starting with data modeling a good choice?
When starting with modeling, the team did not feel so much of a classic problem when applying DDD concepts using relational databases - needing to “dirty” the domain with data mapping objects, for example classes to represent the many-to-many relationship and other infra resources.
The bank and the entities were compatible, and most of the logic was in classes of services. This may be appropriate for a CRUD, but it reveals little about the domain when it comes to more complex systems. Anemic entities do not show the behavior of objects, and there is no emphasis on what the system does, but what data it holds.
In addition, some technical disadvantages end up happening - the use of ORM's typically generates “fat” aggregates, with multiple levels of navigation, and eventually data that is not needed ends up being brought to the domain. Multiple dependencies needed to be injected into the services, which generated a greater effort to code tests, since it was necessary to mock the infrastructure dependencies. All of this contributed to accidental technical complexity.
What actions were taken to reveal more about the domain?
The entities were remodeled to bring only the information directly necessary. The business logic was gradually transferred from the services to the corresponding entities. When an operation involved more than one entity, and was confused about which entity to choose to code the operation, domain services were created. The team opted to use CQRS and Rabbit MQ, which allowed to decouple the services, reducing the number of injected dependencies and making them leaner, facilitating the coding of tests.
What resources were withdrawn because they are not needed?
With the simplification of the modeling, the team realized that there was no need to control operations with the Unit of Work pattern, since, in this domain, only one aggregate was changed at a time. The service standard was gradually replaced by commands and queries from the CQRS standard.
How was the architecture?
The architecture of each domain was divided into four layers. Due to the simplicity of the domains, each included between one and four aggregations. The language used was .NET Core.
In general, what was the result of these decisions?
The team understood that, by using the ubiquitous language and following this directive in the choice of architecture, domain modeling and selection of technologies and patterns, systems are created that are simpler and easier to maintain and expand. Deliveries have been faster, with higher quality and less accidental complexity. The team's perception was positive, generating technical engagement with the solution, and today some of the developers participate in events sharing their lessons learned with Domain Driven Design.
This scenario was very effective to illustrate some key points when applying the DDD:
- Ubiquitous language is more than using the same terms that the key user uses. It is bringing these two realities together, the technical and the business, seeking to overcome the barriers of communication with the most appropriate resources, producing software that effectively reflects the needs of the business. One of the features highlighted here is guided prototyping, a way for the user to explain in practice what he needs with something he is able to visualize and validate, more than a diagram or written documentation, with the guidance of the technical team to avoid worry more about the registrations and auxiliary objects than about the actions and events of the solution.
- Focusing on a single domain makes the distillation, analysis, architecture and coding process simpler. Generalize only if there is a need. Generalizing early changes the focus of the solution and often causes accidental technical complexity.
- When modeling software that is not just a set of entries, prioritize ubiquitous language over data. Then, model the data in a way that suits the domain, highlighting the actions and events, bringing only the data directly needed.
- Only use building blocks and resources that will be useful in your context. In this scenario, Unit of Work was not necessary, but Domain Services was. In another scenario it could be different. You do not need to apply all DDD concepts to say that you are using DDD. Quite the contrary - you choose the features that will facilitate development, make the domain clearer and simplify the code whenever possible.
- Likewise, the architecture should be as simple as the nature of your domain allows. In this scenario, four layers were enough to organize the code cohesively. That expression "don't build a cannon to kill a fly" is quite suitable.
I hope this content is of some value to you. Special thanks to Leonardo Prange, for not only sharing this case but contributing code examples, to Elemar Jr for participating, and to PHPSP, who invited me to the meetup that originated this article. For the PHP audience, one of the meetup participants recommended the book to me Domain Driven Desing in PHP, I had already recommended Scott Millett & Nick Tune's book with examples in C#, another very well known is Vaughn Vernon's “Implementing Domain-Driven Design” with examples in Java and now this one with examples in PHP, showing that these principles can be applied in any language and technology!