Microservices == Macro headaches
TLDR: Unless you really need it and know why you need it, it’s best to stick to a monolithic architecture for your application.
For the purpose of this post, microservices can be defined as loosely coupled services which communicate with each other via some kind of messaging system. Monoliths on the other hand are big old lumps of code where different functionality directly interacts with each other via method calls or at most through the database.
The potential advantages of using microservices are that the application functionality can be split up along logical boundaries and potentially developed by completely different teams. Communication is via clearly defined interfaces, so in an ideal world each team can stay ignorant of the internal functionality of each other’s services and interact only through the exposed API. Services can also be scaled completely independently of each other which provides further flexibility.
It all sounds great in theory and I also jumped on the hype train a few years ago to develop an application using a microservice based architecture. I’m also currently maintaining services which are part of such a system. Unfortunately, like all things in tech there’s never a silver bullet and that holds true for microservices as well. Every decision has its advantages and disadvantages and basing your application architecture around microservices has both benefits and drawbacks.
The key point to keep in mind is that you’re not running Twitter, Facebook or Netflix and if you are, what are you doing reading this post? If you’re running a global scale application with a billion plus users then close this tab, this post is not for you. On the other hand, if you’re like most people handling an application with a much more modest user base then read on.
The fundamental problem with microservices is that they are a form of premature optimization. As with all forms of premature optimization, you’re probably not going to need it; or to be more precise, you’re not going to need it in the form you anticipate. Sure, you get the flexibility of independently scaling different parts of the application, but at what cost?
You’re going to spend more (much more!) time maintaining and debugging your app than you are going to spend developing it. Orders of magnitude more. Development doesn’t become any easier with microservices but debugging becomes a whole lot harder. Just getting some sort of distributed logging to work (you do want to track a user request across services right?) is a major task in itself. Trying to track down a bug across multiple systems each with their own databases is a special kind of hell. And the setup! You don’t have just one app or docker image to finagle, you’ve got to have a whole mini universe of them spun up on your machine just to get a single complete user flow to work. Not only do you not get to view the various services as opaque abstractions, now you need to dig into each to see where the problem might lie.
The problems become even more acute in production. Having the various components communicating via message queues sounds nice in theory, but in practice it makes fixing a running system on the fly much more dangerous. If there’s one database with a complete view of the data in its various states contained within it, then fixing it is straight-forward. If you need to reconstruct and push older messages through the queue to replay a transaction, you’ll be knee deep in doo-doo in no time.
So what’s to be done? Should we abandon the promised land of microservice nirvana and content ourselves with our home-grown balls of mud? Not exactly. The best way forward seems to be to first build your application as a monolith. Then once you understand your requirements intimately and have experience dealing with the application in production, you can distribute your code horizontally for performance or break it up to reduce coupling. In this way, you’ll be targeting those parts of the application which are most performance intensive and will benefit the most from optimization. At the same time, you’ll retain the quick turn around and lower cognitive load of dealing with a monolith for as long as possible.