The microservices fallacy - Part 5
This post discusses the widespread fallacy that microservices lead to better solution design.
In the previous post we discussed two fallacies – that microservices improve reusability (and thus pay for themselves after a short while) and that microservices improve team autonomy. In this post we take a closer look at the fifth fallacy I want to discuss – that microservices lead to better solution design.
Another claim often heard in the context of microservices is that microservices lead to better application design, including claims like you can update individual microservices without having to test the whole system. 1
The underlying reasoning is:
- Deployment monoliths usually end up as “big ball of mud”.
- Microservices on the other side enforce their boundaries due to their very nature.
- This makes it impossible to access them in any other way than via their public interface.
- Therefore microservices lead to better service design (and can be updated individually).
To be honest, I find this kind of reasoning quite disturbing, even dangerous because the underlying assumption is that it is not possible to have a well-structured monolith. Instead you need a different runtime structure to achieve an acceptable source code structure. For me, that feels like a declaration of bankruptcy regarding design abilities and implementation discipline.
Reality is: The chosen runtime structure does not impede your source code structure by any means. Runtime structural units normally are at least as big as source code structural units, usually bigger. Our build tools typically allow for arbitrary mappings from source code to runtime units. This means, you can organize and structure your source code completely independent from the chosen runtime structure.
A deployment monolith can be perfectly structured.
You do not need microservices to achieve an acceptable (i.e., maintainable) source code structure.
Yet, monoliths are rarely well structured. Usually we find deteriorated designs full of dependencies, design violations, quick hacks and worse.
But – and this is the key point here – it is not due to the chosen runtime structure “monolith”. It is due to a lack of knowledge, experience and discipline of the software engineers involved:
- If you do not know how to design a system in a structured and weakly coupled way, you will end up with a “ball of mud”.
- If you skip understanding the functional domain of your system, tight coupling is unavoidable and you will end up with a “ball of mud”.
- If you do not design clear module interfaces and decouple them from the module implementation, you will end up with a “ball of mud”.
- If you decide to bypass the existing module interfaces because it is technically possible and saves you a bit of implementation effort, you will end up with a “ball of mud”.
- If you shun to document the relevant design principles of the system and thus make it very hard or even impossible for future engineers to see them, you will end up with a “ball of mud”.
- If you do not take the time to understand the design of the existing system and just start coding along in your preferred way, you will end up with a “ball of mud”.
And so on. High delivery pressure from the outside works as an accelerant but basically this lack of knowledge, experience and discipline is the root cause for deteriorated source code structure, not the chosen runtime structure. Or as Simon Brown put it:
And this begs the question that if you can’t build a well-structured monolith, what makes you think microservices is the answer? 2
My observation is:
- As long as we do not know how to create good designs, let alone distributed designs, bad designs full of dependencies will be the norm.
- As long as time to complete features is the only thing that is rewarded in development, design violations and quick hacks will be the norm.
- As long as documentation for the future maintainers of a system is treated carelessly, accidental design violations will be the norm.
- As long as we do not have the discipline to adhere to agreed upon designs, “big balls of mud” will be the norm.
If you choose microservices as runtime structure, it will be just a “distributed ball of mud” which is even worse because now you do not only have development hell, but also operations hell.
“Big ball of mud” basically means tightly coupled, highly entangled functionality. As we have seen earlier in this post series, tight coupling across process boundaries leads to availability problems in production.
Overall, it can be said that microservices do not lead to better design. It is exactly the opposite way: You need (a lot) better design skills if you want to use microservices.
And these required design skills are about good functional modularization – yes, modularization. All you need to achieve the design properties attributed to microservices is modularization, a technology available in all programming languages for more than 60 years meanwhile.
If you are able to design functionally encapsulated and decoupled modules, no matter of which implementation type, you achieve the properties you are looking for – that you can update a single part of the system without having to test the whole system.
Looking at the desired properties, it does not matter if you build and deploy a bigger runtime unit from multiple source code modules or if you deploy each source code modules as an individual runtime unit. Today’s CI/CD pipeline have shown that they can automate the differences away from developers.
Still, it makes a huge difference in production. As we have discussed in part 3 of this blog series, microservices increase the system complexity at runtime by at least an order of magnitude. And if you do not know how to properly encapsulate and decouple modules, this will heavily backfire if you use microservices as your runtime architecture style.
You will also not be able to update single microservices independently because dependency is decided on a funtional level – and if you do not know how to functionally decouple your microservices properly, you will still have to test your whole system before you can update a single microservice.
As I have written, e.g., in my Simplify! blog series when discussing accidental complexity at the architectural level, as an industry we still have not understood how to do good functional design.
Currently, Domain-Driven Design is treated as the new silver bullet to solve all the design issues. Yet, as I have written in the aforementioned post knowing what something is does by far not mean that you know how to use it properly. Based from all I have seen in the recent years, as an industry we are still stuck at the functional design knowledge of the 1970s and have not improved since.
Some individuals have improved their functional design skills, but as an industry we are stuck. And if you do not belong to those individuals by chance, microservices will not only not help you with your design issues. Quite the opposite, they will result in much worse problems than you ever had with a monolith.
I will leave it here for this topic and for this post. In the next post, I will discuss the last fallacy from the list: That microservices make technology changes easier. Stay tuned …