The Microservices Misconception
These days one can scarcely attend a conference or browse the front page of HackerNews without running into microservices. Proponents tout its preternatural ability to enforce modularity, scale systems, and scale organizations and processes. Of course, microservices is just a rebranding of the older Service-Oriented Architecture paradigm, and no one is quite sure what “micro” means in the first place, but lets ignore that for now. I have seen the same fallacious arguments repeated again and again when it comes to microservices, so I finally decided I had to write a rebuttal.
Let’s begin with the most egregious misconception surrounding services. Again and again I hear seemingly competent programmers saying how monoliths are bad for modularity and microservices enable it. It seems over the past few years we as programmers have completely forgotten how software systems were built in the preceding decades. Services are not required, or even beneficial, for modularization.
Programming languages are the vehicle through which modularity are achieved. With facilities such as functions, classes, interfaces, and, yes, modules, they enable us to write decoupled code with well-defined interfaces that we can plug together, yet implement separately. This is achieved through two means: namespaces and types. Physically separating the machines is entirely irrelevant. In fact, by making network calls, we actually lose this typing information, as we leave the confines of a single programming environment. REST APIs are actually an extremely weak form of modularity. So, contrary to popular belief, micoservices actually hinders modularity.
Programmers have always strived to write modular code. This has been done since before networks even existed, and will still be done long after the fad of micro-services (hopefully) ends.
As your user base grows, so must your systems scale to meet the increasing load. There are a multitude of methods to achieve scaling, in every facet of engineering.
For some reason, proponents seem to think the only way to horizontally scale your system is to break it up into pieces. This is entirely untrue. We can scale a single application horizontally by adding more servers and through replication. Any provider — AWS, Heroku, GCP — makes this simple to do, even if you only have a single application. The number of services is completely orthogonal to the number of physical machines you’re running. This doesn’t just apply to stateless servers either — we can also scale our database tier by adding read-only replicas, and scale our elastic search cluster by adding more nodes and shards.
I think vertical scaling is also often overlooked. This solution is financially costly, but requires no changes in your application code or infrastructure. When considering the cost, we must also take into account that that cost may be outweighed by the programmer time wasted dealing with the errors and overhead brought on by microservices
Another overlooked approach to scaling is to actually optimize code! Now granted, this can be quite a lot of work, but using a faster language, a more efficient data structure, or a smarter SQL query can have a huge impact on both latency and throughput.
Services should be the very last step in optimizing a pipeline, after every other avenue has been explored.
I will concede this is one area where independent services might assist. It can in some situations be beneficial to have the code base cut into vertical slices to align with product or features. But most organizations simply do not have enough engineers for this to be necessary. If you consistently have engineers working on multiple different services, then it’s the wrong approach.
It’s easy to get swept up in the service hype tornado, but let’s not forget the concomitant costs. They are numerous, so I will simply list them in broad strokes:
- Multiple applications for which you must synchronize deploys and keep versions in sync
- Local development becomes much more difficult as you have to work on several repositories at the same time, and keep changes in sync between them.
- You must handle failures over the network.
- You have to add service discovery so your applications can find each other.
- You will probably have to set up an orchestration system to distribute your apps across a cluster.
- You need multiple databases, which means there is no consistent view of your application state or easy way to create transactions
- You need a way to correlate transactions across your services for logging purposes
Just because I want to avoid the latest fads doesn’t mean I’m resistant to change. As engineers, we must always strive to write the best code we can, creating a stable product that keeps customers and other engineers happy. But we should focus our energy on the fundamentals: writing clean, modular code, identifying bottlenecks and making reasonable optimizations, clear communication within and without the engineering team, and focusing on understanding our current technologies, instead of adding throwing enough buzzwords at the technological wall and hoping something sticks. For the vast majority of organizations, microservices is putting the cart before the horse.