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 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 a bad code into 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 for the same. Since you are dependent on a single pipeline to deploy changes of multiple teams, there are high chances of one team blocking the other – hence causing delays in feature delivery to Business.
I have worked with clients on their Insurance and Manufacturing mission critical 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 taking & also critical in an Integration/Stage environment. Most of the times even the monthly deployments could not be done because new defects were identified during the testing phase couple of days before the Production deployment.
Over the years the code complexity for these applications has grown & components were tightly coupled to 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 few key people who have been working on these systems for an extended period of time. This create dependencies and delays in getting changes reviewed.
Finally when everyone realized that this was not a sustainable model to deliver Business Features, it was decided to make some architectural changes to decompose the monolith application into smaller loosely coupled services. This would enable Teams to deploy changes to production more frequently.
Refactor the Monolith or Rebuild the Monolith from scratch?
Rewriting a large Monolith 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 new enhancements or features you deliver on the current platform. So the Business need to wait to have any new feature developed and pushed out of the door.
Strangler Pattern reduces the above risk. Instead of rewriting the entire application, you replace the functionality step by step. Business value on new functionality is provided much quicker. If you follow Single Responsibility Principle and write loosely coupled code, it makes future refactoring work very simple.
What is Strangler Pattern?
The Strangler Pattern is a popular design pattern to incrementally transform your Monolith 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 & the old component is decommissioned all together.
Any new development is done as part of the new service and not part of the Monolith. This allows you to achieve a high quality code for the greenfield development. You can follow Test Driven Development for your Business logic and integrate SonarQube to your deployment pipeline to not allow any technical debt to accrue.
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. For implementing Strangler Pattern, you can follow 3 steps — Transform, Co-exist 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 as —
- Initially all application traffic is routed to the Legacy application.
- Once the new component is built, you can also test your new functionality in parallel against the existing monolithic code.
- Both the monolith and the new 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 Strangler Pattern for the first time and are new to this design pattern, playing safe and selecting a simple component is not a bad option. This will ensure that you gather practical knowledge and acclimatize yourself about the challenges & 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 lot of confidence during the migration process.
- If there are components which are better suited for Cloud and have scalability requirements, then start with that particular component.
- If there is a component which has frequent Business requirements and hence needs to be deployed 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 up 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. 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. Its 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 Monoliths to Microservice much more 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 microservice 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 about a design pattern called as ‘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 in case you have any questions and I would be happy to discuss.
Good Reading —