Strangler Fig Pattern in Practice: Migrating Monoliths to Microservices Without a Full Rewrite
Learn how the Strangler Fig pattern enables the incremental modernization of legacy systems, replacing old features with modern microservices without the risks of a full rewrite.
Summary
- Total rewrites of legacy systems often fail because they ignore hidden business rules and accumulate chronic delays.
- The Strangler Fig pattern creates a routing facade to intercept requests and gradually redirect them to new code.
- Domain boundaries identified through Domain-Driven Design help determine which parts of the monolith to isolate first.
- Sharing databases early in the migration reduces initial complexity, though caution is needed to avoid long-term coupling.
- Rigorous observability and feature flags ensure that failures in the new service can be quickly rolled back without user impact.
The Legacy Monolith Dilemma and the Risk of Total Rewrites
Almost every growing company faces the same problem: the system that worked well in the beginning turns into a complex monolith, a tangle of code where no one dares make changes for fear of breaking everything. The natural impulse of the engineering team is to throw everything away and rewrite the software from scratch. In practice, this means promising a deadline that is never met, ignoring years of business logic hidden in forgotten corners of the codebase, and delivering a new product that suffers from the exact same issues as the old one. A full rewrite is one of the biggest budget and time sinks in the software industry.
To escape this trap, software engineering adopted a strategy inspired by nature: the Strangler Fig pattern. In the rainforest, the seed of this plant germinates atop a host tree, sending roots down to the soil. Over time, the fig tree grows around the original tree, slowly suffocating it until the host dies and only the new tree remains, hollow inside yet structurally sound. In software development, the idea is precisely the same: build a new system around the old one, replacing pieces bit by bit until the legacy system disappears entirely.
How Traffic Interception Architecture Works
The core of the Strangler Fig pattern is a routing mechanism, usually implemented via a reverse proxy or API Gateway, which acts as a single entry point for all system requests. In practice, this component acts like a smart receptionist at the entrance of a commercial building, checking each visitor's badge and deciding whether to route them to the old offices or the newly renovated wing. When a user clicks a button on the screen, the request passes through this central router, which knows precisely which routes have been migrated to the new microservices and which still rely on the monolith.
To implement this logic, engineers configure redirection rules based on URL paths or HTTP headers. For example, all calls targeted at /api/v1/users continue going to the legacy monolith, while the path /api/v2/users is routed to the new user microservice built with modern technologies. This decoupling allows the transition to happen transparently for the end client, whether it is a mobile app or a web browser, ensuring continuous operational stability throughout the modernization process.
Isolating Domains and Defining Boundaries with Domain-Driven Design
The biggest mistake when starting a Strangler Fig migration is trying to slice the monolith randomly by files or functions. It is essential to use Domain-Driven Design, a project approach that aligns software development with real-world business concepts and processes. In practice, this means mapping the natural boundaries of the business — such as billing, inventory management, or customer registration — and treating each as an isolated domain. Identifying the first domain to extract requires evaluating which part of the system suffers the most frequent changes or presents the most critical performance bottlenecks.
A good initial candidate is usually a peripheral, autonomous feature with few cross-dependencies with the rest of the system, such as an email delivery service or monthly report generation. By choosing an isolated part, the team gains confidence in the deployment process and validates the microservices infrastructure without putting the core financial application at risk. This early success reduces internal resistance to change and provides real metrics on the effort required for subsequent migration steps.
The biggest technical challenge in any monolith migration is not moving the code, but handling the data. In a traditional monolith, dozens of different modules read from and write to the same tables in a centralized relational database, creating an invisible yet extremely rigid coupling. When extracting a microservice, the golden rule is that it must own its independent database, ensuring total autonomy. However, breaking this central database all at once is unfeasible, as legacy features still rely on the same information.
To solve this dilemma, engineers use a transition phase where the new microservice reads and writes to the legacy database, or they employ real-time data replication using event streaming tools like Apache Kafka. Another common strategy is the dual-write technique, where the updated application writes data simultaneously to the old and new databases until the migration is mature enough to turn off the legacy storage. This care prevents data loss and ensures consistency during the period when the old and new systems coexist in the same production environment.
// Conceptual example of a Node.js reverse proxy for Strangler Fig routing
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({});
const server = http.createServer((req, res) => {
// Route new paths to the microservice and the rest to the legacy monolith
if (req.url.startsWith('/api/v2/catalog')) {
proxy.web(req, res, { target: 'http://localhost:4000' });
} else {
proxy.web(req, res, { target: 'http://localhost:3000' });
}
});
server.listen(8080, () => {
console.log('Strangler API Gateway running on port 8080');
});Monitoring, Feature Flags, and Risk Mitigation
Replacing parts of a system in production is like performing open-heart surgery while the patient is awake: it requires millimeter precision and immediate responsiveness to any sign of failure. To maintain control, teams use feature flags, which are configurable switches in the code that allow turning new features on or off remotely without a new deployment. In practice, if the newly created microservice starts throwing timeout errors, the operator can flip the routing switch and instantly send traffic back to the legacy monolith within seconds.
Furthermore, observability based on metrics, centralized logs, and distributed tracing becomes mandatory. Application Performance Monitoring (APM) tools allow teams to compare latency and error rates between old and new code in real time. With this data, engineering can validate whether the migration is delivering the expected performance gains or if there are hidden network bottlenecks between the new microservices. The success of a Strangler Fig strategy is measured not by how fast the monolith is destroyed, but by how smoothly the transition happens for users.
Conclusion and Final Thoughts on Incremental Modernization
Modernizing legacy systems through the Strangler Fig pattern proves that architectural evolution does not need to be a leap in the dark. By breaking the problem into smaller slices and replacing code in a controlled, incremental manner, organizations avoid the catastrophic risks of full rewrites and deliver continuous business value without disrupting operations. This approach turns years of accumulated technical debt into a manageable and sustainable action plan.
Ultimately, the success of this journey depends as much on technical discipline as it does on cultural change within the engineering team. Accepting that the old and new systems will coexist for months — or even years — requires patience and rigor in API governance and data management. When executed well, the Strangler Fig pattern not only replaces old code with modern technology but also empowers the team to deliver software with greater agility, resilience, and confidence for the future.