The Strangler Fig Pattern: A Safe Way to Migrate Monolithic Applications

The Strangler Fig Pattern: A Safe Way to Migrate Monolithic Applications

In modern software engineering, legacy monolithic applications are a common challenge. Over time, a successful codebase grows so large and interconnected that making simple changes becomes risky, deployments take hours, and scaling individual features is virtually impossible.

When teams decide to modernize their systems by migrating to microservices, they face a high-stakes question: How do we rewrite the system without breaking our current business?

One option is a “Big Bang” rewrite—building the new system from scratch behind closed doors and switching everything over in a single day. However, this is incredibly risky and frequently leads to failure.

Fortunately, there is a safer, more reliable alternative: the Strangler Fig Pattern. In this guide, we will explore what the Strangler Fig Pattern is, why it works, and how to apply it step-by-step using clear diagrams and real-world code.


The Real-World Analogy: The Strangler Fig Plant

The pattern is named after the strangler fig, a plant native to tropical rainforests.

A strangler fig seed germinates in the upper branches of an existing “host” tree. Instead of growing from the ground up, the fig grows downward:

  1. It sends roots down the host tree’s trunk until they reach the forest floor and anchor in the soil.
  2. Over time, more roots grow, wrap around the host, and fuse together.
  3. The fig grows leaves that block light from reaching the host tree.
  4. Eventually, the host tree dies and rots away, leaving a hollow strangler fig tree standing strongly in its place.

In software architecture, the legacy monolith is the host tree, and the new microservices are the strangler fig. We build the new services around the edges of the monolith, gradually shifting traffic away from the legacy system until the monolith can be completely shut down.


Why “Big Bang” Rewrites Fail

Before diving into the mechanics of the Strangler Fig pattern, let’s understand why the alternative—a complete rewrite—is so dangerous:

  • No Value for Months (or Years): Developers spend a long time writing code, but none of it goes live until the entire project is completed.
  • Scope Creep: During a two-year rewrite, business needs change. The target moves, and the new system has to support features that didn’t exist when the rewrite started.
  • Missing Implicit Behavior: Monoliths contain years of undocumented bug fixes and edge-case handlings. A clean-slate rewrite often forgets these details.
  • High Deployment Risk: Turning off a massive legacy system and turning on a new one all at once creates a massive blast radius if something goes wrong.

How the Strangler Fig Pattern Works

The core idea of the Strangler Fig Pattern is incremental migration. Instead of migrating the whole system, you migrate one small feature or “slice” at a time.

Strangler Fig Pattern Migration Stages Diagram

The migration process is executed in five key stages:

1. Identify a Bounded Context

Look at your monolith and identify a single, self-contained business capability that is easy to extract. Good candidates include:

  • Features that change frequently (so the team benefits quickly from independent deployments).
  • Simple, low-risk features (like a static FAQ or user preferences section) to test the migration pipeline.
  • Features with clean, well-defined database boundaries.

2. Implement the New Microservice

Build the identified capability as a brand-new, modern microservice. This service has its own database, its own deployment pipeline, and is built using modern tech stacks. Crucially, the old feature in the monolith remains active and unchanged for now.

3. Introduce the Interception Layer

To make the migration transparent to your users, you introduce an Interception Layer (such as an API Gateway or Reverse Proxy) in front of the application. All client traffic now goes to this gateway first.

Initially, the gateway routes 100% of all requests to the legacy monolith.

4. Transition Traffic Incrementally

Once the new microservice is fully tested and ready, you update the routing rules in the Interception Layer. Instead of routing requests for the migrated feature (e.g., /api/users) to the monolith, the gateway redirects them to the new microservice.

All other requests continue to go to the monolith. If the new service fails or exhibits bugs, you can quickly update the gateway to route traffic back to the monolith, ensuring minimal disruption.

5. Decommission and Repeat

After the new microservice runs stably for a period of time, you can safely delete the corresponding code inside the legacy monolith.

You then select the next feature and repeat the process. Over time, the monolith shrinks until it has no traffic remaining, and you can decommission the legacy server entirely.


The Interception Layer: Express Routing Example

The heart of the Strangler Fig pattern is the Interception Layer. It allows you to redirect requests without modifying the client applications (web apps, mobile apps).

Here is a practical Node.js example using an Express-based gateway proxy. It routes requests dynamically: incoming requests go to the new services if they match migrated paths; otherwise, they fall back to the legacy monolith.

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const app = express();
const PORT = 8080;

// Configuration: Target server URLs
const LEGACY_MONOLITH_URL = 'http://legacy-monolith-server:3000';
const NEW_USER_SERVICE_URL = 'http://new-user-service:3001';
const NEW_PAYMENT_SERVICE_URL = 'http://new-payment-service:3002';

// Simple logging middleware to track traffic distribution
app.use((req, res, next) => {
    console.log(`[ROUTE LOG] Incoming request: ${req.method} ${req.url}`);
    next();
});

// 1. MIGRATED: User registration & profile requests route to the new microservice
app.use('/api/users', createProxyMiddleware({
    target: NEW_USER_SERVICE_URL,
    changeOrigin: true,
    pathRewrite: {
        '^/api/users': '/v1/users', // Translate path format if necessary
    }
}));

// 2. MIGRATED: Payment transactions route to the new payment microservice
app.use('/api/payments', createProxyMiddleware({
    target: NEW_PAYMENT_SERVICE_URL,
    changeOrigin: true
}));

// 3. FALLBACK: All other legacy routes automatically default to the Monolith
app.use('/', createProxyMiddleware({
    target: LEGACY_MONOLITH_URL,
    changeOrigin: true
}));

app.listen(PORT, () => {
    console.log(`Interception Gateway routing traffic successfully on port ${PORT}`);
});

Handling the Database: The Hardest Part

While routing API traffic is relatively easy, managing data is the most challenging aspect of monolithic migration. A monolith usually has a single, massive database where tables are highly joined.

When you extract a service, you must also extract its data. There are two common approaches to handling this:

  1. Dual Writes: The interception layer or the application writes data to both the legacy database and the new service database simultaneously during the transition phase. This keeps both databases in sync.
  2. Change Data Capture (CDC): A tool like Debezium monitors the legacy database transaction log and automatically streams changes to the new microservice database in near real-time.

Once you are confident the databases are fully synchronized and the new database is correct, you switch the read traffic to the new service and shut down the legacy tables.


Comparison: Big Bang Rewrite vs. Strangler Fig

Feature / Metric Big Bang Rewrite Strangler Fig Pattern
Risk Level Extremely High Low and Managed
Feedback Loop Very Slow (only at the end) Fast (continuous production testing)
Rollback Strategy Hard (requires restoring backups) Easy (change route rules in gateway)
Business Impact Disruptive Zero downtime
System Complexity High (during build phase) High (during migration phase)
Deployment Time Massive single release Small, frequent updates

Conclusion

The Strangler Fig Pattern is the industry standard for migrating monoliths to microservices. By replacing legacy code incrementally rather than all at once, you eliminate the risk of a “Big Bang” failure.

It keeps deployments small, provides instant feedback from real production traffic, and allows your team to continue delivering business value throughout the migration lifecycle.

While managing database migration and running two parallel systems adds operational complexity, the safety, predictability, and stability it offers make it the preferred choice for modern cloud migrations.


Explore more software development and backend engineering insights on the Ghaznix Blog →