Over the past years I attended hundreds of interviews. Many candidates proudly told tales on how they develop their projects with a microservice architecture. Often (I don’t want to say “always”, but from my memory I think it actually is “always”) it does not require many questions to see that they used a rocket launcher to kill a mouse. Microservices are hard. Everyone who experienced the pain of operating such an architecture can relate to it. The complexity kills you at one point or the other. You already had to do multiple refactorings of your architecture - because your domains didn’t work out. I wonder - why is this architecture so appealing to developers? And then I remember what I found them appealing 10 years ago.
What most of the stories have in common is that they had to work on a legacy monolith which code base is a big ball of mud. This situation is frustrating. Implementation take ages, writing tests is tedious - if not practically impossible, understanding the code is hard, bugs are piling up, deployments are unreliable - simply every change… hurts. Refactoring the legacy code seems impossible and thinking about this monster of a task makes you headaches and keeps you awake at night.
That’s when microservices become appealing.
The thought about small, manageable and seperated code bases awakes the feeling of safety and relief that you miss at the moment.
But, it comes with a cost. Costs which are easily underestimated.
The Bad - Incorrect Domains
A microservice architecture only works when you slice your domain correct. This is hard. Indeed, very hard. You need to know what you build. And, most of the time you don’t. Even if you think you do. Committing to a domain makes your system inflexible - the opposite of what you actually hoped for.
At the point in time when you slice the domains you might not know all the product requirements. Probably a feature will arise which forces you to tangle two services together - now you have one domain. But distributed. Urgs.
The Ugly - Complexity
After the initial eventstorming the architecture appears to be very easy. The architect is (pretty) sure that it can’t be violated and the team feels comfortable in the small code base. Things change when you start deploying your services. You need to “quadrupel” everything. Dashboards, Monitoring Systems, Log Aggregators, Deployments, CI Jobs, alerts, documentation…. And you introduce services just because you need them for your architecture. You need distributed queues, shared caches, shared databases, service discoveries, multiple load balancers (you solve that with client side load-balancing. Of course…), dynamic routers, API Gateways, a central configuration server (and vault) and so on. I’m sure I forgot something.
You changed from a big ball of mud in your code to a pile of mud in your infrastructure. Large companies like Uber learned this the hard way:
For the record, at Uber, we're moving many of our microservices to what @copyconstruct calls macroservices (wells-sized services).— Gergely Orosz (@GergelyOrosz) April 6, 2020
Exactly b/c testing and maintaining thousands of microservices is not only hard - it can cause more trouble long-term than it solves the short-term. https://t.co/VL8opOh1BY
The Evil - Premature Optimization
For decades, we divide our software into services. We just didn’t call them “microservice”. We split an application for two reasons.
First, when it makes sense for the organization.
It is obvious that there should an independent team working on the customer relationship management tool and the e-commerce platform. Even though they need to communicate to one another at some point. This is already a service oriented architecture. To achieve the single business goal of selling products we need two services. We just never called it microservice architecture.
And second, for performance.
In my experience this reason affects maybe 1% of all the software that’s build out there. With just a couple of hundreds of users you can scale vertically at ease! I developed systems with tens of thousands concurrent users which heavily interacted with the system - and we were still able to scale vertically.
So, what do we want?
We identified an issue. The big ball of mud in our legacy monolith. A microservice architecture is just not the right solution for the problem. What you are seeking are modules. Proper modules. Modules which makes it very hard to violate the boundaries. Modules which can be easily be deployed independently - when the need arises. This is by no mean a new idea - it is just not exciting. And often it is not done properly.
10 years ago I wanted modules, but I as well found microservice architectures. It’s time that we put an end to this over-engineering.
A practical approach how you can do that with Spring and Gradle I’ll present in another blog 😁