Strangler Design Patterns

Strangler design pattern is used to perform the transformation from monolith to microservices. Microservices architecture has many advantages compared to monolithic architecture. Hence, many organizations start building new applications with microservices or convert existing monoliths to microservices.

However, transforming a large-scale monolithic application to microservices from scratch is a challenging task. It requires significant architecture and development effort and poses a higher risk to the organization. Nevertheless, some well-established patterns and practices may help to reduce these risks.

Why do we need the Strangler Pattern?

Over 80% of organizations worldwide use microservices, and 17.5% plan migrating their existing architectures to microservices. But, writing an existing monolithic application from scratch with microservices is a challenging task. As mentioned, it involves severe risks and requires significant developer effort.

  1. Time-consuming - Rewriting an application can take a significant time. Especially if your application is large-scale, it can take up to years. Also, there can be unexpected issues that can take a considerable amount of time to resolve.
  2. Can't develop new features - The rewriting process requires a colossal developer effort. You might have to divert the whole development team to the migration task, which will slow down the new feature development.
  3. Uncertainty - You must wait to use the new system until the development process completes. Then only you can ensure that all the existing functionalities are working as expected

The Strangler Pattern minimizes these risks and uncertainties while providing a way to gradually refactor monolithic applications to microservices rather than writing from scratch.

What is the Strangler Pattern?

Strangler Pattern is a software design pattern used to refactor monolithic applications to microservices gradually. It helps developers to replace parts of the monolith with new and improved components while maintaining the same functionality.

The Strangler Pattern uses a wrapper to integrate the microservices with the monolith. This wrapper is an integral part of this design pattern since it bridges the monolith and the microservices, directing incoming requests to the appropriate component for processing. Furthermore, it acts as a fail-safe, allowing the organization to roll back to the monolith if there are any issues with the new microservice.

How does a Strangler Pattern work?

Refactoring a monolith into microservices with the Strangler Pattern consists of 3 main steps: Transform, Coexist, and Eliminate.

  1. Transform: You need to start by identifying the main components of the monolithic application. This step involves identifying the boundaries between the existing application and the new components being developed.
  2. Coexist: Then, build a wrapper around the monolith to allow the new components to coexist with the existing application.
  3. Eliminate: Finally, eliminate the monolith by replacing parts with new components. However, you must ensure that each microservice works as expected before integrating it into the system.

Example

Consider an e-commerce application with a monolithic architecture. The application will include user registration, product search, shopping cart, payment handling, inventory management, and more. To give you a better understanding, I will divide the migration process into five steps.

  1. Step 1 - Deciding the first microservice
    As the first step, you need to individually identify the monolith components with their capabilities and limitations. Then, it would be best to decide on the first microservice you will migrate. There are several factors to consider when ordering the components for refactoring. In this example, I have decided to use product search functionality as the first component to migrate since it is not in the application's critical path. Also, it is crucial to identify the dependencies between the selected component and others. In this example, the product search component is directly connected with inventory and shipping cart components.
  2. Step 2 - Creating the new microservice
    As the second step, you must start creating the new microservice and move all the relevant business logic to the new microservice. Then, it would help if you created an API for the microservice, which will act as the wrapper to handle communications between the monolith and the microservice.
    After implementing the microservice, you need to run both the monolith's component and the microservice in parallel to verify the functionality.
  3. Step 3 - Handling the databases
    When you create a new microservice, it is necessary to create a separate database. In addition, since we are maintaining both monolith and microservice in parallel for a time, we also need to keep the primary and microservices databases in sync. For that, we can use a technique like read-through-write-through caching. In this example, we will use the microservice database as a cache until we remove the product search component from the monolith. Then, when a user searches for a product, we look in the microservices' database. We can fetch the data from the primary database if it is not there.
  4. Step 4 - Handling requests
    Initially, routing a small percentage of traffic to the new search service is advised to reduce the blast radius of the new microservice. For that, you can use a load balancer.
  5. Step 5 - Test and repeat
    Once you verify the functionality of the new microservice, you can remove the component in the monolith and route all the traffic to the microservice. Then, you must repeat these steps for each component and gradually convert the monolith into microservices.

How to select the component order for refactoring?

Another major issue developers face when using the Strangler Pattern is the component order for refactoring. Although no hard rules are defined for the selection process, it is essential to sort out what components should be migrated first to avoid unexpected delays

  1. Consider dependencies: It is good to start with components with few dependencies, as these are likely to be easier to refactor.
  2. Start with low-risk components: Starting with low-risk components will minimize the impact on the system if something goes wrong in the early stages. Also, it will help you to gain experience and build confidence in the process.
  3. Business needs: If there is a high-demand component and you need to scale it as soon as possible, you should start with that component to facilitate the business requirement.
  4. Components with frequent changes: If components require frequent updates and deployments, refactoring them as microservices will allow you to manage separate deployment pipelines for those services.
  5. User experience: Start with the components with the most negligible impact on end-users. It reduces disruptions and helps to maintain a good user experience during the transition.
  6. Integrations: Refactor components with minimal integration with other systems first.