There was an Underappreciated Wisdom in the Monolith
Myself and others have sung the praises of microservice architectures. A popular trend for the last ten years. Has it delivered on all of its’ the promises?
The promises of microservices:
- Microservices promised… increased velocity for developers
- Microservices promised… decreased code coupling
- Microservices promised… increased scalability
Has the promises of microservices been delivered?
- Microservices promised… increased velocity for developers
- Why do you need to modify three microservices for one change? The feature needed 100 lines of code no matter where it was written. Was it indeed faster to write those 100 lines split between three services instead of one?
- Don’t you need to write (at least) three integration tests now? Wouldn’t it have been easier to write some unit tests in one service?
- Microservices promised… decreased code coupling
- When you update an API signature for one service do you have to modify three other services to use the new signature? Do you have decreased code coupling? You had to update all those consumers. Whether in the same service or not.
- Microservices promised… increased scalability
- How many times have you scrambled to scale up one microservice because it could not handle the load? How many hours did you spend last year staring at graphs to decide if you have enough instances of each service? Does the auto-scale system really work? Does it operate fast enough to react to load increases without customer impact?
Were those points really weaknesses of the monolith?
- Microservices promised… increased velocity for developers
- Finding dependencies between code pieces was easier back when we have monoliths…
- The IDE detects all usages of a class, function, variable, etc.
- The debugger transverses all function calls.
- The stack-trace has all context.
- The code changes for a single feature change can be represented in a single code review for one service with full context.
- The unit tests can cover more of the logic. Fewer integration tests are needed which are less preferable.
- The complete test suite proves the complete project is functional. Deploy this green build in confidence.
- Finding dependencies between code pieces was easier back when we have monoliths…
- Microservices promised… decreased code coupling
- A lot of monoliths had undesired code coupling… but the solution should have been to not write highly coupled code.
- Did you really need a network call between services to prevent your coworker from inappropriately coupling those two classes?
- Microservices promised… increased scalability
- There are two kinds of scale questions to always consider…
- Do I have enough hosts in my fleet to handle my customer traffic (independently of what is deployed on each machine. If not, then buy more computers)
- Do I have the “optimal” number of instances for each service placed over the collection of services (if not the allocate/deallocate instances)
- With the monolith you are only concerned about the first scale question (which is much simpler). You are not concerned about the second one.
- With a monolith, you never “waste” compute cycles because one service is only 10% utilized and another is 100% utilized and overloaded.
- In a monolith, if you have some code paths that are being used more than others then… that is fine. The system has used more CPU for those instructions than others. Which is the whole point of scaling and auto-scaling: to give more compute to operations that need more compute and less compute to operations that need less.
- There are two kinds of scale questions to always consider…
Myths of the monolith and the microservice (and non-myths)
- Fact or Fiction: microservices can be written in any language. Monoliths have to be written in all one language.
- Not true. Almost every programming language has bindings to execute functions in almost any other programming language. You can call a C++ executable from NodeJS and pass the data back and forth.
- Some of you read the above point and said “yeah, but there is a performance hit”. It’s 1/100 of the performance hit of doing a microservice network call.
- Some of you read the first point and said “yeah, but you have to serialize and deserialize the response”. Yes. Just like when you do a microservice network call.
- Fact or Fiction: microservices can have multiple databases, one for each service. Monoliths have a single database.
- This has nothing to do with service code. You can connect either to as many databases as you like. Just have a connection string to a database and connect.
- microservices can be deployed independently. A monolith has to be deployed as one unit.
- Who said that a monolith has to be deployed as one unit? Or even that all of the code has to be in one software package (ex: JAR, DLL)? You can have multiple software packages (and it is typical to do so). In some programming languages, you can even load and unload software packages from memory on the fly without stopping the process.
- Fact or Fiction: microservices need a bunch of retries for unreliable networks. When code is executing in a monolith it is a function call from one piece of code to another and it will always “work” without a retry and there is dedicated silicon in the CPU to make that operation super fast and is the result of decades of processor design optimization costing billions of dollars of research and development.
- Yes. This is true. Not a myth.
Finally, the case for microservices
After reading all of this you may ask “why did you say you and others sing the praises of microservices? This chapter sounds like a denouncement of microservices in favor of monoliths.” Not exactly.
I am trying to rebut all of the myths and false promises people have been singing about microservices for the last decade and haven’t resulted in our paradise of highly scalable bug-free non-highly coupled software systems. Take a look at the count of bugs in your bug tracker. Obviously we are not in heaven.
microservices did not solve our technical problems; if anything it just made more technical problems (or increased the size of our existing technical problems):
- Larger need for retries (which have an impact on latency)
- More code needed to have resilience to outages from dependent services (because there are more dependent services)
- Need to “right size” scale each individual microservice
- Larger need for auto-scalers, distributed tracing solutions, etc.
- Need for service meshes. Larger need for reverse proxies, load-balancers, etc.
- Need for S2S authentication, intra-service encryption, etc.
So why do microservices? Alas, we are now ready for the only important sentence in this chapter. I hid near the end where only the smartest people would find it.
microservices do not solve ANY technical problems, they ONLY solve people problems
We do microservices because we can’t easily have a large number of people coordinate together without fighting. We do microservices to solve people problems, not technical problems.
A discussion of people problems worth fixing, is a topic for a future article.