Monolith to Microservices Using the Strangler Pattern
Moving from a monolith to a series of microservices poses many challenges. See how the Strangler pattern can help make this transition smoother.
Join the DZone community and get the full member experience.Join For Free
Working in a legacy codebase has its own challenges. With the advent of new technologies, organizations have found it difficult to continue delivering and staying competitive with their monolithic legacy applications. To keep up with the market demand, its necessary to continuously evolve your applications. Transforming your current legacy applications into a number of small microservices seems to be the best approach.
Maintaining a legacy application is cumbersome and often leads to additional work because of multiple reasons:
- Lack of unit tests
- Violation of the Single Responsibility Principle
- High complexity leads to more time spent in maintenance activities
- Inability to scale individual components to meet increased demand
- Tight coupling between components makes it difficult to deploy regularly
- Technical debt accumulated over time and makes future development difficult
Consider a scenario where you have multiple teams working on a monolithic application. If Team B deploys bad code to a lower environment, it is going to block all the remaining teams from pushing any of their code changes until Team B pushes a code fix. Since you are dependent on a single pipeline to deploy changes from multiple teams, there is a high chance of one team blocking the other, causing delays in feature delivery to the business.
I have worked with clients on their mission-critical insurance and manufacturing applications operating on older technologies with manual deployments. Even though there were multiple teams working on making changes to cater to new business requirements, we were not able to deploy the monolithic application regularly. We used to have monthly releases since manual testing of the new changes was time-consuming and also critical in an integration/staging environment. Most of the time, even the monthly deployments could not be done because new defects were identified during the testing phase a couple of days before the production deployment.
Over the years, the code complexity for these applications has grown and components were tightly-coupled with each other, which makes it difficult to implement good automated testing around the codebase. Making a simple change in one class has the potential to break an existing functionality implemented in an interconnected module. Rather than relying on failing tests, we have to depend upon a few key people who have been working on these systems for an extended period of time. This creates dependencies and delays in getting changes reviewed.
Finally, when everyone realized that this was not a sustainable model to deliver business features, we decided to make architectural changes to decompose the monolithic application into smaller, loosely-coupled services. This would enable teams to deploy changes to production more frequently.
The 2022 Trend Report on Kubernetes in the Enterprise.
Download the Report
Refactor the Monolith or Rebuild It From Scratch?
Rewriting a large monolithic application from scratch is a big effort and has a good amount of risk associated with it. One of the biggest challenges for us was having a good understanding of the legacy system. We did not want to carry forward the technical debt associated with the legacy system into our new modern system.
Also, if you go ahead with rewriting the monolith from scratch, you cannot start using the new system until it is complete. You are in a corridor of uncertainty until the new system is developed and functioning as expected. Depending upon the size and complexity of the application, this might take over a year in a lot of scenarios. During this period, since you are busy developing the new system, there are minimal enhancements or new features delivered on the current platform, so the business needs to wait to have any new feature developed and pushed out of the door.
The Strangler Pattern reduces the above risk. Instead of rewriting the entire application, you replace the functionality step by step. The business value of new functionality is provided much quicker. If you follow the Single Responsibility Principle and write loosely-coupled code, it makes future refactoring work very simple.
What Is the Strangler Pattern?
The Strangler Pattern is a popular design pattern to incrementally transform your monolithic application into microservices by replacing a particular functionality with a new service. Once the new functionality is ready, the old component is strangled, the new service is put into use, and the old component is decommissioned altogether.
Any new development is done as part of the new service and not part of the Mmonolith. This allows you to achieve high-quality code for the greenfield development. You can follow Test-Driven Development for your business logic and integrate SonarQube with your deployment pipeline to prevent any technical debt from accruing.
In the below diagram, the Order Service is eventually strangled from the monolith into an independently deployable service with its own CI/CD pipeline. Team A is now not dependent upon any issues with other teams.
You need to have processes in place to streamline this transition from monolith to microservices. To implement the Strangler Pattern, you can follow three steps: Transform, Coexist, and Eliminate.
You can develop a new component, let both the new and the old component exist for a period of time, and finally terminate the old component.
Based on the diagram below, the steps can be summarized:
- Initially, all application traffic is routed to the legacy application.
- Once the new component is built, you can test your new functionality in parallel against the existing monolithic code.
- Both the monolith and the newly built component need to be functional for a period of time. Sometimes the transitional phase can last for an extended duration.
- When the new component has been incrementally developed and tested, you can get rid of the legacy monolithic application.
How do you select which components to strangle/refactor first?
- If you are following the Strangler Pattern for the first time and are new to this design pattern, playing it safe and selecting a simple component is not a bad option. This will ensure that you gather practical knowledge and acclimatize yourself to the challenges and best practices before strangling a complex component.
- If there is a component which has good test coverage and less technical debt associated with it, starting with this component can give teams a lot of confidence during the migration process.
- If there are components which are better suited for the cloud and have scalability requirements, then start with one of those components.
- If there is a component which has frequent business requirements and hence needs to be deployed a lot more regularly, you can start with that component. This will ensure that you don't have to redeploy the entire monolithic application regularly. Breaking it into a separate process will allow you to independently scale and deploy the application.
The cloud migration journey is not an easy one and you will bump into multiple hurdles. The Strangler design pattern assists you in making this journey a bit painless and risk-free since you are dealing with small components at one time. It's not a big undertaking when you plan to do the migration in increments and small pieces.
Reducing the complexity of an application enables you to deliver business features faster. It also allows you to scale your application based on increasing load. Having an automated CI/CD pipeline makes it a lot easier to deploy the microservices and can make the transition from monolith to microservices much smoother.
When you are finally able to decompose your monolithic application into a number of microservices, the representation will look like the diagram below. Each microservice has its own data store and CI/CD pipeline.
Transforming your existing legacy monolithic application into cloud-native microservices is a nice end goal to have, but the journey is challenging and needs to be well architected and planned. In this article, we discussed a design pattern called the "Strangler Pattern," which can assist you in this journey. Teams can continue delivering business value by doing all greenfield development as part of new services and incrementally migrate from monolith to microservices. However, keep in mind that strangling a monolith is not a quick process and may take some time.
Please let me know if you have any questions and I would be happy to discuss them.
Published at DZone with permission of Samir Behara, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.