The first Microservices talk I attended was a year and a half ago. My first reaction was “why is that something new?”. Then I realized it is already getting overhyped, so I listened to some more talks, read a bit more articles, so that I can have a good reason not to like the hype.
What are microservices is probably defined here or by Martin Fowler, or at any of the first google results for “microservices”. It is basically splitting up your functionality into separately deployable modules that communicate with each other in order to complete a business goal, and each microservice is limited to just a small, well-defined scope. Product purchasing is one microservice, user registration is another microservice, “current promotions” is another microservice, and so on. Or they can be even more fine-grained – that appears to be debatable.
And whenever I encounter a new “thing”, I try to answer the questions “what’s the point” and “does this apply to me”. And I’d like to point out that I’m not the person that rejects anything new, because things can be done “the old way”, but there’s a fine line between a good new technology/architecture, and hype. And besides, microservices is nothing new. I remember several years back when we had an application split into several parts that communicated with web services. When I joined, I refactored that into a single monolith, which improved response times by around 20%. It turned out we never needed the split.
And that’s why I’m going to write about – that you probably don’t need microservices. And Martin Fowler has phrased this very well:
…don’t even consider microservices unless you have a system that’s too complex to manage as a monolith. The majority of software systems should be built as a single monolithic application. Do pay attention to good modularity within that monolith, but don’t try to separate it into separate services
There, done. Now go build a monolith. But microservices advocates wouldn’t agree and will point out all sort of benefits of a microservices architecture (or will point out that your system is too complex, so you have to use microservices. And pay them for consultancy). So let’s examine a few alleged advantages that microservices have (for example around 30:57 of this video). The question I’m going to ask is – can this easily be done in a monolith?
- modeled around the business domain – absolutely. You can structure your packages and runtime dependencies around the business domain.
- culture of automation – that has nothing to do with the architecture – you can automate the deployment of any application. (We are doing an automated blue-green deployment for a monolith, for example).
- hide implementation details – that’s what object-oriented programming is about. Your classes, and your packages, hide their implementation details and expose interfaces. Microservices bring nothing to that (except the network overhead). You can even still have multiple projects, built as dependencies for the main project.
- decentralize all things – well, the services are still logically coupled, no matter how you split them. One depends on the other. In that sense, “dcentralized” is just a thing that sounds good, but in practice means nothing in this particular context. And is maybe synonymous with the next point.
- deployed independently, and monitored independently. That alone doesn’t give you anything over a monolith, where you can gather metrics (e.g. with statsd) or get profiling and performance information about each class or package.
- isolated failures – now that’s potentially a good thing. If one module “fails”, you can just display “sorry, this functionality doesn’t work right now” and handle the failure. A monolith, however, doesn’t have to fail completely either. It is the details of the failure that matter, and I’ve never seen any detailed explanation. A server goes down? Well, you have a highly-available cluster for that, regardless of how your code is structured.
Some more, loosely defined benefits like “easy to modify” and “easy to understand” are claimed. But again, a well written, structured and tested monolith can be as easy to understand and modify.
Basically, a lot of commons sense, software engineering, continuous integration/delivery and infrastructure management best practices are claimed to be a bonus of microservices, while in fact they work perfectly fine with a monolith.
The ability for a graceful degradation is possibly an important aspect of microservices, but again, you can handle it with a monolith as well – it would require a little bit of extra code – e.g. feature if’s that are toggled in case of failures. But it’s nothing compared to the extra effort you have to put in place in order to get a working microservices application.
And that’s a lot – you have to coordinate your services. You have to decide what to do with common data. And the usual suggestion is “duplicate it”. If two microservices need some common data, they can’t just use the same shared database – each microservices has its own database, so it has to duplicate the data. Which sounds easy, unless you have to keep that data in sync. Which you always have to do. And when you have to keep duplicated data in sync accross 5 microservices, the overhead possibly exceeds any possible advantages.
Another big problem are transactions. You either don’t need transactions (in which case – lucky you), or you end up with (if I may quote Martin Kleppmann) “ad-hoc, informally-specified, bug-ridden slow implementation of half of transactions” (here’s the whole talk about transactions).
The microservices communication overhead is also not to be underestimated, including the network overhead and all the serialization and deserialization. And my controversial advice to use a fast, binary format for internal communication, rather than JSON/XML, is rarely seen in practice.
So, I would again recommend to follow Martin Fowler’s advice and just stay with a monolith. Do follow best practices, of course, including:
- Modularity – separate your logic into well defined modules (will be easier with project jigsaw), define your class public interfaces carefully, and use loose coupling within your codebase
- Continous delivery and automation – automate everything, deploy often
- Be highly available – make your application horizontally scalable, be as stateless as possible, and make failures undetectable by end users
But don’t believe when someone tells you these best practices are features of the microservices architecture. They aren’t, and can be done pretty easily in a monolith, without the side effects – you don’t have to think about keeping duplicated data in sync, about network overhead, about writing a half-assed two-phase commit mechanism, about coordinating the services, etc. And if you really think you have to do “microservices”, be sure to have pretty good reasons for it.