Microservices is not a Buzz word anymore. It is one of the Architectural patterns that has gained lot of traction. Everyone wants to do Microservices — either as a Green field or ripping apart the existing Monolithic application. Many of the teams actually know why they want to go the Microservices route but there are quite a few who want to do it because everyone else is doing it or may be because they have been asked to do so !!!
Starting a new Product Development with Microservices Architecure may not always be a good idea. One has to be abolute clear with the Service boundary context, etc. There are trade-offs to be made. In-fact — it is a very debatable topic and one has to be very careful with the delivery complexities Microservices brings !!!
Now, assuming you already have a Monolithic application and you want to decompose it into multiple Microservices — where do you start? Or may be we should first find out, what problems one could be facing with the Monolith application which is making them think to decompose it. Let’s look at some of them —
- Application has grown way too large and complex to fully understand. Hence making a simple change is not a cake walk anymore. And maintenance activity requires more time.
- Team extension/On-boarding a new Developer in the team means high ramp-up time.
- A minor change in the least used feature of the application leads to deployment of the entire application and hence requires regression testing of the entire application just to make sure that change is not breaking anything else. Impacting Time to Market.
- One module is very volatile in nature. The code gets changed very often, but entire application needs to be deployed. So regression testing, etc. comes here as well.
- If there is a specific module of the application which needs vertical scaling, one still needs to scale the entire application. Hence, you have to pay for high compute and/or memory resource as the entire Monolith application has to be scaled.
- A single bug in one of the module can take the entire Application down, impacting availability of the system.
- If different modules of the component have conflicting requirements — one needs high compute and other needs high memory, you need to pay for a machine which has both — high compute and high memory. Hence high cost.
- Adopting to new Technology stack is not possible as it will impact the entire application.
- Build time is high, impacting Developer productivity.
- Application start-up time is high.
- Reduce the scope of the audit — Even though only one module of the application needs to be compliant with an standard but the entire application needs to be audited as it’s part of the monolithic application.
Now that we understand why are we breaking the Monolith and what is the goal we want to achieve, the next obvious question is — Where to start? Which modules should be taken out first and how do we eventually release it in production for others to consume. Below points can help you decide on it.
- Identity the probable modules which can be candidates of Microservices. Use Domain Driven Design to identity the right modules. Assuming an E-Commerce Reference Application — it could be Order Management module, Catalog module, Notification module, etc.
- Identify which module has low complexity and is easy to migrate.
- And importantly — Identify which module will actually help you to achieve your Goal.
Based on the above points, one can freeze on a single module or may be a set of modules for moving them out as Microservices. Once identified, below mentioned patterns can be leveraged to decompose the application, realize the new Microservice and release for others to consume.
Pattern 1 — Strangler Pattern
This pattern lets you move out a feature module at the edge of the Monolithic application, handling traffic directly from the external clients, into a standalone Microservice. The pattern helps to implement the Microservice and divert the traffic to the new service and eventually strangle the feature running in the Monolith. The old and new implementation can co-exist for some time but eventually the old implementation will go away once the new implementation (Microservice) is able to handle the traffic in production environment. In the context of E-Commerce application, modules like Ordering, Catalog, Payments can be a fit here. There is no impact on the behavior of the Application as a whole. And hence there is no impact on the external clients consuming the application. To realize this pattern, one should start as mentioned below —
- Write the service (assuming Order service) as a Microservice using the right set of Technology stack — if you want to get out of the Technical debt. You could also copy the existing module code and refactor it.
- Create CI/CD pipeline for the new service.
- Deploy the Service to handle the load.
- Important one — Have interceptor (Proxy layer) in place at the edge to divert the traffic targeted for the Order Management feature to the new Microservice. In case of AWS, an existing ALB for the application can do the trick — using path based routing where URL path with /orders can be sent to the new Service.
- Traffic can be diverted incrementally, canary, or in one shot.
- Because the actual feature is still running as part of the Monolithic application, it is easy to fall back in case the new Service has issues.
- The underlying Data store can be shared between Monolith and new Microservice until the cutover is done.
- Once satisfied with the performance and behavior (this is not going to change off-course) of the new Service, one can start removing the Order Management feature code from the Monolithic application. And start thinking of moving the Ordering data to a new Database which will be completely owned by the Order Service. This is the next level of decomposition — Data Decomposition.
Strangler Pattern in Pictorial view —
Pattern 2 — Branching By Abstraction
This pattern lets you move out an internal module of the Monolithic application into a standalone Microservice. The module does not directly handle the traffic at the edge and is used internally by other modules. Classic example of such module could be — Notification module of the E-Commerce Reference application. Other modules use Notification module to send Payment related emails, Order confirmation emails, SMS for Multi-Factor Authentication, etc. This pattern also allows to have both — the existing feature implementation and the new Service co-exist for some time. Which one to be used can be controlled using the Feature toggle method. To implement this pattern —
- Very important — Create an abstraction point in the system for the module which needs to be moved out.
- Let other modules use this abstraction (Interface in case of Java language) for using the feature (lets say Notification to send notifications). This might need some refactoring in other modules.
- Ensure the existing module (Notification for example) implements this abstraction.
- Move the feature out by developing a new Microservice using the right set of Technology stack. You could also copy the existing module code and refactor it.
- Create CI/CD for the new service and release in the production environment for other modules to use.
- In the Monolithic application, create a new abstraction implementation which talks to this new Microservice over Rest API or may be via Message Broker. From the caller modules perspective, they are zero impacted as they have coded to abstraction.
- Use Feature toggle method to switch between the implementations.
- Once the new Microservice is working as expected, the original implementation can be removed from the Monolith.
Branching By Abstraction Pattern in Pictorial view —
To conclude, both these patterns help you to gradually move out the modules (internal or edge facing) from the Monolithic application and carve out as Microservices, which can be implemented in the choice of Technology, can be scaled independently and deployed independently. But let’s not forget the overheads it brings.
The more distributed the application is, the more serious efforts needs to be considered to ensure High Availability, Fault Tolerance, etc. etc.
Give it a try and enjoy the world of Microservices !!!