In my previous post, Thoughts on Microservices, I tried to describe what a microservice is. This article will focus on why you should or should not adopt a microservice architecture.
When to adopt a microservice architecture?
Before you start adopting a microservice architecture, you might want to know if it is the right fit for you.
The main reason for adopting a microservice architecture is breaking a large monolith. If your current system consists of one or a few large components that are hard do maintain, that is an indication that you might want to consider a microservice architecture. However, if you are a small team with a simple product, the overhead of creating and maintaining multiple microservices might do more harm than good.
If you are starting from scratch, with no legacy system, my recommendation is to adopt a microservice architecture if you are planning to develop a larger system, spanning multiple domains (boundedContexts).
What's in it for me?
So, what do you gain from a microservice architecture (MSA)?
The one thing most people strive to reach with MSA is reduced coupling.
Even though reduced coupling within the system is the main goal, it is not a direct consequence of MSA. If you are not careful you might actually increase coupling when moving to a MSA.
Reduced coupling will allow you to release components of the system independently, which will hopefully improve the over all efficiency of your team.
One of the benefits of MSA is the ability to, just like LEGO, build a large complex system from small simple components (microservices).
With LEGOs, you can replace one piece of the construction with another piece without having to rebuild the entire structure. Similarly, in the microservice context, you can patch a faulty microservice or update a service with a new feature without having to rebuild any other microservice in the system.
A microservice can even replaced with completely new microservice, written in a different language and potentially running on a different platform, without effecting the system as a whole. If that doesn't convince you of the power of microservices, nothing will.
We've already defined a microservice as a small and independent application, but let's dig deeper into the benefits of independent applications.
With independent applications, you should be able to develop, test and deploy the application in complete isolation.
The benefit of developing in isolation is that you can be completely technology agnostic. Two microservices within the same ecosystem can be developed by different developers, in different languages and with different dependencies.
Developers can therefore choose the technologies they are experts in or see most suitable in each situation. One service can be developed in Python with MySQL as datasource while another can be developed in C++ using Scylla as datasource. All depending on the requirements on each individual service.
This is called technology heterogeneity and can improve both stability, performance and release cycles. It is only when communication between service are introduced that you have to lay some common ground of how services should communicate with each other.
The Dark Side of Microservices
Unfortunately microservice are not all roses and unicorns. So, what problems do microservices introduce?
Doesn't solve the main issue
The first thing to note is that microservices does not solve anything by default. For example, the issue of maintaining a complex system might not be solved just because you adopt MSA. It can actually be quite the opposite, instead of one large monolith to maintain and deploy you now have multiple small services that have their individual release cycles. And the larger your system grows, the harder it will be to deploy and orchestrate all these small independent applications. Fortunately, smart people are already solving this problem with orchestrating tools such as Kubernetes and Docker Swarm.
The Curse of Technology Heterogeneity
Having high technology heterogeneity might lead to some problems. Since no software exists in a static environment, there will come a day when it has to be updated or deprecated. If your team has high technology heterogeneity chances are that your current team of developers might not be able to update a legacy microservices developed by former developers. That microservice will eventually rot and become a burden, reducing the team efficiency and introduce risks. The only good news is that it should not be a big hurdle to replace the service with a new one. In the end, companies will often settle on one or a few technologies and thus decrease technology heterogeneity.
Keeping it DRY?
Another problem related to technology heterogeneity but on the opposite side of the spectrum is the problem that arises with shared code. Chances are that similar, or exactly the same, code will be used in multiple services. You might end up with services that share a majority of the codebase with other services. Duplicated code increase the codebase and goes against the Don't-Repeat-Yourself (DRY) principle. The more technology homogeneous your system is, the more likely it will end up containing vast amounts of duplicated code.
There are two approaches to handle this. Either you accept the duplication of code between services, knowing that it will increase the over all codebase, or you can create a shared library with common code. Both solutions have its pros and cons. The obvious one with duplicated code is that a larger codebase will be harder to maintain and rot faster. You also risk having to update the same code in multiple places. With the shared library or shared package approach, duplicated code will be refactored in to a library that can be included in multiple services. This will reduce your codebase but creates tighter coupling between your services. If you are not careful when implementing shared libraries you might end up with a monolithic system consisting of multiple microservices, where a change in one service will force a change in another service.
The Multi-Service Monolith
Shared code is not the only way to end up with a monolithic microservice system. Communication might also lead to a multi-service monolith.
If the system is a monolith or not, come down to how tightly coupled your services are. When starting out with a small number of services, one might not care too much for what services talk to each other. However, as the system grows with new services, it will be come increasingly hard to keep track of how all the services communicate.
This problem can easily be avoided by putting some thought in to what services should be allowed to talk to each other and create some structure of how communication flows in the system. One option is to create different zones for each business domain. Within each zone can be more loosely structured and should not have any effect on the communication within other zones. Communication between zones must, however, be well though out and structured. A good approach would be to have one dedicated service to act as an interface for all communication in and out of the zone. For the rest of your, system each business domain will now look as a single service, even though there might be multiple services below it. This will keep your system nicely decoupled and maintainable.
A microservice architecture will not solve all your problems by default. If you are not careful you might actually end up with system that is harder do maintain than before.
Even though a microservice architecture has its downsides, these are fairly easy to deal with or avoid all together. In the end the benefits of a microservice architecture will strongly outweighs the drawbacks as the system grows.