Recently, microservices are a very popular word spoken in the IT industry. What is it and is it really a panacea for monolith ills?
Some time ago during the construction of many IT systems very popular was SOA architecture (service-oriented architecture). Its idea was to connect together many systems divided into services. The assumption of SOA architecture was to eliminate the weaknesses of the so-called monolith. Typically, this architecture was approaching the so-called ESB or Enterprise Service Bus. All communications between exchange services were via the bus being served. Unfortunately, over time it turned out that the rail can be the weakest link in all solutions and often resulted in its failure causing many problems. The more systems were plugged into the bus, the more changes on the document mapping bus, routing support was needed and bus itself was slowly becoming a monolith.
So what are microservices really?
Probably while you are reading this, you are wondering – “But how are microservices different from SOA?” You are right – microservices are really an implementation of the SOA style, but their assumption is based on the conclusions drawn from errors made when using the ESB.
Microservices provide us with many benefits:
- First of all, the system becomes more resistant to failures. Failure of one website does not cause problems in the work of others.
- Modules become almost fully autonomous. They operate as separate instances, are developed by separate teams and even maintained in separate repositories.
- Easy scalability. Thanks to microservices, we can freely scale those modules that are the most loaded. In the case of a monolith, this applied to the entire application.
What is the exact difference between a monolith and microservices?
In a monolith, all components of the solution are interrelated and interdependent. Usually, there is also one database. In the case of monolithic architecture, failure of one element or increased load within a given functionality cause problems with the operation of other components.
In the case of micro-services, each bounded context runs in a separate instance. Services can communicate directly with each other but it is somewhat contrary to the idea of low coupling. Therefore, communication between sites should take place asynchronously using e.g. message broker with the highest possible level of transaction. Additionally, each website should have its own, separate database instance.
Below is a simple diagram illustrating the differences between the architecture of the monolith and microservices.
Looking at the architecture of microservices you can ask yourself – OK, but how are we supposed to communicate with so many endpoints? And should we expose them to the outside world? Well, a solution called the API Gateway comes to our aid. It is a kind of facade whose task is to hide the complexity of infrastructure by application clients.
If you want to use the API Gateway, we recommend ready-made solutions such as Kong, Ocelot, NGINX Plus.
Boundaries of microservices
One of the most common questions asked when designing microservices is how to separate their boundaries. In this case, use the concept called Bounded Context, which is known from Domain Driven Design. Deciding for this approach, we must, together with the domain experts, try to accurately determine any Bounded Context present in the system. Thanks to this, we will ensure order and consistency of our solution. But not only Bounded Context should set limits on microservices for us. Very often, the planned load of system fragments and technologies in which we want to write selected microservices should be taken into account.
Design for failure
When designing systems using microservices, we must do it so as to be prepared for failure of service. Any error in the operation of the microservice should return a friendly message to the customer. In addition, we must ensure that messages sent between sites despite the failure of one of them finally reach their destination or in the absence of receiving the message, we should be able to verify what and when it happened and at what stage the problem occurred. To do this we should for example implement Saga/Process Manager patterns in our application. Continuous infrastructure monitoring is also very important so that you can react quickly to emerging problems.
Will microservices solve all my problems?
It seems that microservices are the solution to all monolith problems. Unfortunately, it does not look so perfect in reality. First of all, when designing microservices it is difficult to immediately separate Bounded Contexts. Secondly, we are never entirely sure what the load will be within individual modules. Thirdly, it is very difficult to ensure an appropriate level of transaction transactionability in a distributed environment. Considering the above, microservices are a very interesting solution that can solve many problems, but they need to be planned very well and then implemented in a correct way. A very good solution is to start designing and implementing the system from a modular monolith and then smigrate to microservices based on the knowledge how the modular monolith works with life scenarios. This will allow the production environment to better verify our division and make any corrections before implementing the microservice architecture.