After several years of the microservice hype, now it seems monoliths are cool again! Does it mean, we have learned a lesson? I guess at least we accepted what Neal Ford stated in his book Building Evolutionary Architectures:
If you can’t build a monolith, what makes you think microservices are the answer?
The disturbing question is: Why is monolith synonymous with bad design for some and the right thing to do for others?
The reason is, there are two different kinds of monolith. While one is good, the other is pure evil.
Two Kinds Of Monolith
The first kind of monolith is what we usually picture under the word: a physical block of software, typically running as a single process. A physically monolithic system is developed and built as a single artifact, deployed at once and falling down as whole. Resources such as a database are often shared, communication is local, interprocess.
The opposite is then a distributed system composed of multiple physically independent components each running in its own process. Each component owns its resources and communication is done in a remote manner.
A physical monolith is no anti-pattern, it’s a good thing to start with as it’s easy to build, deploy, operate and reason about. Physically monolithic applications are pretty performant as there are no additional overheads in communication. Cross-cutting aspects are much simpler because no special platform (such as a service mesh) is needed.
As the system gets bigger, further partitioning is possible. Popular option is to apply a “satellite” architecture where bottleneck services are separated from the monolithic base.
The second kind is a logically monolithic system. Another names are the Big ball of mud, Spaghetti code, etc. Logically monolithic codebases lack boundaries (technologies are not service boundaries!), everything is coupled to everything, no visible architecture is to be found. Logical monoliths are the ones we call evil by right.
The opposite is a modular monolith (or, if you like, modulith). In a modular codebase business capabilities are worked out by services with explicit logical (not necessarily physical) boundaries. A modular monolith is probably the best architectural approach for most applications. A modular monolith is cool!
Although the logical and physical nature of monoliths are independent, they often come hand in hand. That’s why people easily confuse them with each other.
As the boundaries in monolithic codebases are typically not physical it’s easy to cross them. A monolithic codebase therefore requires great discipline.
A logically, but not physically, monolithic system is called a distributed monolith. Distributed monoliths have all drawbacks of distributed systems with almost no benefits. While dealing with the Big ball of mud is pain, distributed monoliths are a real disaster.
Systems often end up as distributed monoliths while adapting the microservices approach incorrectly.
From Microservices To Monolith
The microservices movement in the last years promised us a lot. The catch is, microservices focus only on physical monoliths, not the logical ones.
To understand why, we have to take a closer look at what microservices really attempt to solve.
A microservice is a service with some technical additions (independent development cycle). It is important to notice the word “technical” — as the logically monolithic design is obviously a logical, not technical, issue, there is nothing microservices could potentially do for us! Microservices propose a solution to tackle physical monoliths only. That’s the reason so many attempts to build microservices failed badly, simply because a wrong issue was addressed and the true problem got only bigger (and, even worse, distributed).
With logically monolithic design, microservices don’t come to the rescue, rather to put down.
We can think of microservices as a specific approach to Service-oriented architecture (SOA). There are several definitions of SOA, but we will focus mainly on the concept of service, because it is the most significant. I use this modified service definition from Udi Dahan:
A service is the autonomous unit of logic for a specific business capability.
Now, it is obvious why microservices as such can’t really help us with the logically monolithic design: monolithically designed microservices are no services at all, they are mere physical components!
Only business has the key to defining our services correctly. And only well-designed services can tackle logical monoliths and profit from the microservices approach.
It’s a hard task, but Domain-driven design can help us a lot!
If you struggle with your monolithic system, the problem may likely lie in its logically monolithic design. The physical nature of the monolith is usually a secondary problem, easy to solve after proper service-oriented design has been applied. Once the logical monolith is resolved, microservices architecture is just one step further…
You may check out my example project in Java built on the principles of modular monoliths: