#3 Dilemma: Modular Monolith or Microservices?
The burning issue for architects is when to move toward microservices and when to move toward the safe haven that is the modular monolith. Let's pragmatically find out which way to go.
Recent years have been marked by microservices. Who among you hasn't heard of them? They were everywhere - at conferences, groups, job listings, coffee talks, and blog posts. At some point, it got to the point where if you weren't working on a distributed system, it was kind of a problem - you felt the world was getting away from you, and your colleagues were in another galaxy. Someone found a silver bullet for the design of IT systems.
On the other hand monolith. Very often associated with a big ball of mud. Slow, not scaleable, a change in one place affects many others (if done correctly, none of these is true). In general, the worst that can happen to you. Such a narrative made most of us want to try something new, an elixir that would cure all the ills of software development. We got bitten by monolith, let's try microservices.
After a while, we began to see problems with microservices that weren't there before. Network latency, eventual consistency, choreography vs orchestration, incorrectly set bounded contexts (ouch!), and many more. It turned out that it was not as beautiful as we had previously imagined. We got bitten by microservices, let's try to get back to the monolith, this time correctly modularized.
Such is life. In the end, we still have to answer the question of which of the two approaches to choose for our application.
How do I make a choice?
In the vast majority of cases, you should select modular monolith for the beginning (note that I am talking about the beginning). We usually start in an unknown - requirements are missing, no one knows how the application will be received in the market or in the case of legacy, which of the business cases that were modeled 10 years ago are not anymore valid, and so on.
Mostly you don't need Google scale either - unless you need it, then microservices are quite a good fit.
So, why make your life more complicated?
Instead of having multiple deployment units and complex infrastructure, you have a single one and you do not need to worry about network latency or errors. Gain experience, try out your application, see what you need, and then consider whether you want microservices or not.
To be ready at any time to extract one of your modules into a separate microservice, you need two things - correct modularization and loose coupling.
If done incorrectly, you will migrate from a monolithic big ball of mud to a distributed one :)
Otherwise, extraction of a module will take you 1-2 weeks.
Your choice.
What the hack are these modules?
When I talk about modules, I take into consideration the fact that for me a module in modular monolith = microservice in a distributed system. This way when I feel the need to extract some part of the modular monolith, I just take one module and it becomes a microservice. What is then the boundary of such a module? For me, it is a single subdomain or a group of them - in any case, they create a bounded context. I treat it as a rule of thumb.
Of course, as time passes, architecture and business evolve, boundaries will change - that's why it's so important to pay attention to change - nothing is given forever. There might be a chance that one of your subdomains or a process inside of it will grow or be frequently used and it will become a new module or a microservice.
The fewer modules are tightly coupled with each other, the easier it will be for you to pull them out to separate deployment units. Even in a modular monolith, you can loosely couple them using simple queues. Pay attention to this the next time you plan your application architecture.
When to switch to microservice(s)?
Well, now you know that you can switch to a distributed architecture at any time based on the real need. But what will be the determining factor?
Such factors are called disintegrators.
There are many of them, I will describe the most important ones from my point of view:
Going off the scale: up to a certain point, scaling the entire application is not a big problem and can be achieved in a monolith. However, when your application becomes very popular, sooner or later it might be unprofitable to scale more in the selected model. It's that time for you to choose those modules that are used most often and extract them to separate microservices. At stake - scaling vs. extraction costs.
Frequency of changes: there is a possibility that code in several modules will be changed way more often than in the others. The problem is that each time you have to deploy a single unit that contains e.g. 6 modules that were not changed and 2 that were. If the same situation occurs more and more often, this is an indicator of change - take these 2 modules and extract them.
Level of security: in some applications there are areas that require increased attention when it comes to security. If the change you are making will affect the entire application, perhaps it is an ill-advised choice. Analyze the situation, and see if you also need such a high level of security elsewhere. If not, maybe this is the right time to spin off this one module.
Usage of concrete modules: at some point you realize that the module responsible for calculating the potential tax is widely used by your customers. However, it is the only one used so often. Every day you have to scale the entire application to handle the traffic. It costs money and takes time. This can be a good factor to extract and then scale only this.
The size of the system: in the beginning, the business that you work for had in mind some scope of service. However, as the years passed and more companies were acquired, it turned out that the domain in which it operates grew to unimaginable proportions. Modules (bounded contexts) were split between new teams. In the end, there are 10 teams, each responsible for 5 modules. The problem is that they all need to deal with code in a single repository and one deployment unit. This is the perfect indicator to split it into distributed services.
As it happens in life, in addition to the cases mentioned, there are many more. In my opinion, the above ones are the most important. This is the way I approach the separation of microservices from the monolith. In the case of a monolith, where the code is spaghetti I recommend first separating modules, observing them, and only then extracting microservices.
How do you choose between monolith and microservices? What is the deciding factor for you?