Strangler Fig Pattern
Introduction
In today’s software world, there are many large, monolithic software projects. These projects can be hard to keep up with this change rate and can become complex and difficult to understand over time. This complexity can lead to a decline in code quality. There are two main approaches we can use to refactor a legacy system using modern technology: the big bang model and the strangler fig model. In this article, I will focus mainly on these topics.
Big Bang Model
As the title of the method suggests, it aims to convert the old legacy project directly into a modern stack project. All the logic is rewritten at once, and progress is made accordingly. However, this has several disadvantages. Large legacy monolithic applications may have legacy logic and services that have been used for years. The people who worked in this area may have left the company, or there may be structures that have been forgotten because they have been running perfectly for years. It is essential to analyze these well and make the right decisions. Analyzing and restructuring a complex monolithic application of this size can take years of work, which can lead to high costs.A lot of time, effort and resources are required to implement this approach.
Enhancements to the legacy system need to be regularly tracked and migrated, which means constantly juggling between two tasks and thus increasing time.
Strangler Fig Pattern
The name of Strangler Fig Pattern comes from a type of plant. The characteristic of this plant is that its buds are carried as parasites and live and grow there together. As it grows, it eventually wraps around and suffocates the host tree, taking its place. Drawing inspiration from this phenomenon, let’s see how this pattern is used in the world of software.
In this section, we will examine the Strangler Fig Pattern from two perspectives: first, transitioning from a legacy monolithic application to a new modern monolithic application; and second, transitioning from a legacy monolithic application to a modern microservices architecture.
Transitioning from Legacy Monolithic Application to Modern Monolithic Application
The Strangler Fig Pattern provides a sustainable and scalable approach for transitioning from a legacy monolithic system to a modern system. This enables a more controlled and systematic renewal of the system. As illustrated in the diagram above, we have explained how the Strangler Fig Pattern is applied. First, a specific part of the legacy system is identified, such as the ‘order service.’ Next, all dependencies associated with this service are analyzed. Then, the ‘order service’ is recreated in the new system, incorporating current requirements and resolving previous issues. Afterward, a ‘Strangler Facade’ class is created to redirect traffic, allowing both services to operate simultaneously. Over time, the ‘order service’ in the old system is removed, and the new system is fully utilized. This process is repeated for each domain, and in the end, we have an application written entirely with modern technology.
Transitioning from Legacy Monolithic Application to Modern Microservices Architecture
The Strangler Fig Pattern involves migrating a monolithic application to a new microservice architecture using a Domain-Driven Development (DDD) approach. The legacy monolithic application is understood, analyzed and divided into domains. Then, a selected service is rewritten with the new technology. Then, an API gateway is used in the microservices to help with traffic routing. The main goal here is traffic routing, which can be facilitated through an API gateway if desired. Monolithic and microservice implementations then coexist. Over time, as the microservices start to function properly, the services in the monolithic application are strangled (suffocated) and removed. The steps involved are as follows: identification, transformation, coexistence and elimination.
Another name for the Strangler pattern is the Transform, Co-existence, Eliminate pattern. However, in reality, there are much more detailed topics to consider: Especially, questions such as how the pipeline structure of newly created microservices will be, whether data duplication or single storage will be preferred during co-existence, and many others need to be addressed.
Benefits of the Strangler Fig Pattern
- Incremental Migration: The Strangler Fig Pattern allows you to migrate gradually, replacing parts of the system one at a time rather than all at once. This reduces the risk and stress of a big-bang migration and lets teams focus on specific components.
- Risk Mitigation: By making small, controlled changes, it’s easier to spot and isolate issues without impacting the entire application. This approach reduces downtime and allows for safer testing.
- Continuous Delivery: The pattern supports frequent updates, enabling teams to develop, test, and deploy new features continuously, without waiting for a complete system overhaul.
- Flexibility: As components are replaced, teams can choose the best technologies for each part, experimenting where needed without being limited by the legacy system.
- Minimal Disruption: Users don’t face sudden changes, as the old and new systems run side by side. This smooth transition helps prevent confusion and maintains a consistent user experience.
- Feedback Loop: Testing each new component as it’s introduced provides real-time feedback, allowing teams to improve as they go and adapt based on user insights.
- Legacy System Decommissioning: Piece by piece, the legacy system is retired, leaving a fully modernized application without the risks of a single shutdown or added technical debt.
Implementation of The Strangler Fig Pattern
In this section, we will examine the transition from a legacy monolithic application to a modern monolithic application through an example. In this example, we will use Spring Boot and Java version 17. To enhance clarity, we chose to focus on the order service with a simple application.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hacisimsek</groupId>
<artifactId>strangler-monolith</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>strangler-monolith</name>
<description>Demo project for Spring Boot</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Our pom.xml
file is attached, which includes dependencies for Lombok and Spring Web Starter.
package com.hacisimsek.strangler_monolith.service;
public class LegacyOrderService {
public String createOrder(String orderDetails) {
return "Order created through Legacy system: " + orderDetails;
}
public String getOrderStatus(String orderId) {
return "Legacy system - Order status: Processing";
}
}
Let’s imagine a legacy order service here that cannot be updated and is unable to solve its chronic issues.
package com.hacisimsek.strangler_monolith.service;
public class ModernOrderService {
public String createOrder(String orderDetails) {
return "An order was created through the new system: " + orderDetails;
}
public String getOrderStatus(String orderId) {
return "New system - Order status: Processing (with detailed tracking)";
}
}
Now, let’s imagine a modern service written with new technology that has resolved its chronic issues.
package com.hacisimsek.strangler_monolith.facade;
import com.hacisimsek.strangler_monolith.service.LegacyOrderService;
import com.hacisimsek.strangler_monolith.service.ModernOrderService;
import org.springframework.stereotype.Service;
@Service
public class StranglerFacade {
private final LegacyOrderService legacyOrderService;
private final ModernOrderService modernOrderService;
private final FeatureToggleService featureToggleService;
public StranglerFacade() {
this.legacyOrderService = new LegacyOrderService();
this.modernOrderService = new ModernOrderService();
this.featureToggleService = new FeatureToggleService();
}
public String createOrder(String orderDetails) {
if (featureToggleService.isNewOrderSystemEnabled()) {
return modernOrderService.createOrder(orderDetails);
}
return legacyOrderService.createOrder(orderDetails);
}
public String getOrderStatus(String orderId) {
if (featureToggleService.isNewStatusCheckEnabled()) {
return modernOrderService.getOrderStatus(orderId);
}
return legacyOrderService.getOrderStatus(orderId);
}
}
We have examined both of our services; now let’s take a look at our Strangler Facade class. The main goal here is to direct traffic so that whichever service is accessible will handle the request, minimizing the chance of issues. Additionally, I utilized a Feature Toggle Service for traffic management, which operates on a flip-flop logic.
package com.hacisimsek.strangler_monolith.facade;
import org.springframework.stereotype.Service;
@Service
public class FeatureToggleService {
public boolean isNewOrderSystemEnabled() {
return true;
}
public boolean isNewStatusCheckEnabled() {
return false;
}
}
Now let’s create a FeatureToggleService
using flip-flop logic to route traffic. This toggle will enable us to manage traffic based on service availability.
package com.hacisimsek.strangler_monolith.controller;
import com.hacisimsek.strangler_monolith.facade.StranglerFacade;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
private final StranglerFacade stranglerFacade;
public OrderController(StranglerFacade stranglerFacade) {
this.stranglerFacade = stranglerFacade;
}
@PostMapping
public String createOrder(@RequestBody String orderDetails) {
return stranglerFacade.createOrder(orderDetails);
}
@GetMapping("/{orderId}")
public String getOrderStatus(@PathVariable String orderId) {
return stranglerFacade.getOrderStatus(orderId);
}
}
Let’s separate our Order controller from the legacy order service and connect it to the Strangler Facade class. Then, we can observe its operation.
Open the terminal and run the following curl commands to observe and test the application’s behavior.
# Create an order through the new system (feature toggle: true)
curl -X POST http://localhost:8080/api/orders -d “New order”
# Status query via legacy system (feature toggle: false)
curl http://localhost:8080/api/orders/123
“Failure is not the opposite of success, it’s part of success.” — Arianna Huffington
Finally, the Strangler Fig Pattern has allowed us to transition from a legacy system to a modern application in a controlled, incremental way. By addressing dependencies and chronic issues step-by-step, we’ve ensured stability and reduced risks, ultimately achieving a more robust and scalable system.
Project In Github
You can access all source codes of the project from this link.
Find Me
Resource
https://www.geeksforgeeks.org/strangler-pattern-in-micro-services-system-design/
https://learn.microsoft.com/en-us/azure/architecture/patterns/strangler-fig
https://en.wikipedia.org/wiki/Strangler_fig
https://medium.com/agesa-i̇ş-teknolojileri/strangler-fig-pattern-7ac0c56c1c79