Modernization · 9 min read · Oct 12, 2026
Replace the legacy system in slices: a working guide to the strangler fig pattern
How to retire a legacy system piece by piece while the business keeps running: the facade, the first slice, parallel runs, and when the pattern fails.
A full rewrite asks the business to wait a year or two for a system that does what the old one already does. The strangler fig pattern removes the wait. A thin interception point goes between callers and the old application, capabilities migrate to new code one by one, and each retired piece of legacy code gets deleted once its replacement has proven itself. It works well when three things are true, and it fails in a predictable way when they are not.
Why the rewrite keeps losing
The big-bang rewrite has an attractive plan and a bad risk profile. All the value arrives on one date, at the end. Until that date, you pay for two systems and benefit from one. Requirements keep changing in the old system while the new one is being built, so the target moves. And the cutover is a single high-stakes weekend where every hidden behavior of a fifteen-year-old codebase gets discovered at once, in production, by your customers.
The strangler fig approach, a name Martin Fowler gave the pattern after the tree that grows around its host until the host is gone, changes the shape of the risk. Each step is small, each step ships, and each step can be undone. You never bet the company on one release.
The cost is real, and it is worth stating up front: you run two systems for the whole migration, and you have to build and operate the plumbing that connects them. The pattern trades one large risk for a longer period of moderate complexity. That is usually a good trade. It is not free.
The facade is where the work actually happens
Everything depends on one component: a layer that sits between callers and the legacy system and decides, request by request, who answers.
At the start, the facade forwards everything to the old system and changes nothing. That first deployment is boring on purpose. It proves that you can stand in the path of production traffic without breaking it, and it is the single most valuable milestone in the first month.
After that, the facade gains routing rules. "Requests for customer address lookups go to the new service. Everything else goes to the old one." Moving a capability means changing a rule. Rolling back means changing it back.
What the facade looks like depends on how callers reach the legacy system:
| How callers reach the old system | Where the facade goes |
|---|---|
| HTTP or web UI | Reverse proxy or API gateway routing by path, host or header |
| Direct database access from other apps | A service API in front of the tables, with callers migrated onto it one by one |
| File drops and batch jobs | An intake step that decides which pipeline processes each file |
| Message queues | A router or topic split that sends message types to old or new consumers |
| A desktop client talking to a database | Usually the hardest case; often you need a new UI for the migrated slice and a link to it from the old one |
Three rules keep the facade healthy.
First, it routes and it does not think. The moment business rules start living in the proxy configuration, you have built a third system that nobody planned to maintain.
Second, routing has to be switchable without a deployment. A feature flag or a config value that can be flipped in seconds is what makes a slice reversible in practice, not only in theory.
Third, it must be able to route a fraction of traffic. Sending five percent of requests, or one customer segment, or internal users only, to the new path is how you find problems while they are still small.
Picking the first slice
The first slice sets the tone for the whole program. Choosing it badly is the most common early mistake, and it usually goes wrong in one of two directions: too trivial to prove anything, or so central that it takes nine months and the sponsor loses patience.
A good first slice has these properties:
- A clean seam. There is an identifiable set of entry points (a few URLs, one screen, one batch job) that you can intercept without touching everything else.
- Limited data entanglement. It reads and writes a small number of tables, and ideally few other modules write to those same tables.
- Visible value. Someone outside engineering will notice that it got better. A read-only report nobody opens proves the plumbing and nothing else.
- Bounded size. A team can take it to production in weeks, not quarters.
- Real traffic. It needs enough usage to exercise the facade, the deployment pipeline and the monitoring under honest conditions.
A simple way to choose is to list candidate capabilities and score each one on business value, coupling to the rest of the system, and risk if it misbehaves. You are looking for high value, low coupling, moderate risk. The module that causes the most pain is often the most tangled one, so resist starting there. Take it third or fourth, after the team has the routing, the data sync and the rollback routine working.
Picture a regional distributor running a twenty-year-old order management system. The pricing engine is the thing everyone complains about, but it reaches into inventory, contracts, credit limits and invoicing. Order status lookup, by contrast, is read-mostly, is hit thousands of times a day by customers and reps, and touches a handful of tables. Status lookup goes first. It delivers a faster customer portal, and it forces the team to build the facade, the read replica or data feed, and the monitoring. Pricing goes later, with all that infrastructure already paid for.
One more test: prefer a first slice that includes at least one write. A purely read-only slice postpones every hard question about data ownership, and those questions are where migrations stall.
Data is the hard part
Routing requests is straightforward. Deciding where the truth lives is not.
For every slice, pick exactly one system of record for each piece of data at each point in time. The workable options are:
- New service reads from the legacy database. Fastest to start. It couples the new code to the old schema, so treat it as temporary and hide the schema behind a translation layer.
- Legacy remains the writer, new system gets a copy. Change data capture or scheduled sync feeds a new store. Good for read-heavy slices. You must accept and measure the replication delay.
- New system becomes the writer, legacy gets a copy. Needed once a slice is truly migrated but old modules still depend on the data. The sync now runs in the other direction.
- Both write. Avoid it. Dual writes without a single owner produce conflicts that surface weeks later as reconciliation tickets.
The step from option 2 to option 3 is the real cutover for a slice. Plan it as its own event, with a rehearsal.
Shadow, compare, cut over
Do not switch a slice on faith. Run the new implementation next to the old one and compare what they produce.
The sequence that works:
- Shadow. The facade sends each request to the old system, returns the old answer to the caller, and also sends a copy to the new system. The new system's response is recorded and discarded. For writes, the new path must run against a sandbox or in a dry-run mode so it produces no side effects (no emails, no payments, no duplicate orders).
- Compare. A job diffs old and new outputs. Every mismatch is classified: a bug in the new code, a bug in the old code that the business has been living with, or an intentional difference. The second category is always larger than anyone expects, and each case needs a decision from someone who owns the process, not from a developer guessing.
- Canary. Route a small share of real traffic to the new path and serve its answers. Watch error rates, latency and the business numbers that the slice affects.
- Ramp and switch. Increase the share in steps. At one hundred percent, keep the old path alive and callable for an agreed period.
- Decommission. Remove the old code, the old tables that nothing else uses, the sync jobs and the routing rule. The slice is not done until this step is done.
Step 5 is the one that gets skipped. An old code path that is "probably unused" stays forever, and the system you were shrinking stops shrinking.
Three conditions that break it
The pattern is not self-executing. It fails in three recognizable ways.
There is no seam. If there is no place to intercept calls, there is nothing to route. This happens with thick clients bound directly to a database, with logic buried in stored procedures and triggers that fire regardless of who writes, and with systems where every module calls every other module through shared memory or shared tables. You can sometimes create a seam first (wrap the database in an API, move triggers into a service), but that is a project in its own right and it has to be budgeted honestly.
The data cannot be divided. When forty modules read and write the same wide tables, each slice you extract still depends on the whole. You end up with new services on the outside and one shared legacy database in the middle, which gives you the operating cost of a distributed system and the coupling of a monolith. If the data model cannot be split along the lines of your slices, fix that or pick different slices.
The organization stops halfway. The first slices deliver most of the visible benefit. The remaining ones are less glamorous. Funding moves to something else, and the company is left running both systems, plus the facade and the sync jobs, with no end date. This is strictly worse than either finishing or never starting. The defense is to make decommissioning an explicit, funded deliverable of every slice, and to get agreement on the end state before the first one starts.
What to track
Counting migrated screens tells you about effort. These tell you whether the legacy system is actually dying:
- Share of traffic or transactions served by the new path, per capability and overall.
- Legacy surface removed: tables dropped, jobs retired, lines of code deleted, licenses cancelled. If this number is flat while "migrated" goes up, you are accumulating, not replacing.
- Mismatch rate during shadow runs, and how long open mismatches stay unresolved.
- Lead time per slice, from start to decommission. It should fall as the team learns. If it rises, the remaining slices are more coupled than the plan assumed.
- Sync health: replication lag and reconciliation failures between the two systems.
- Incidents caused by the migration itself, and how long rollback took in each case.
- Run cost of the dual estate, so the price of dragging the migration out is visible to whoever controls the budget.
When a different approach is better
The strangler fig is a default, not a law. Skip it when:
- The system is small. If a competent team can rebuild and test the whole thing in a couple of months, the facade and sync machinery cost more than they save.
- A packaged product is the right replacement. If you are moving to a commercial ERP or CRM, the vendor's migration path, not incremental strangling, often dictates the sequence. You may still phase by business unit or region.
- The business process itself is being discarded. There is no point in carefully reproducing behavior you intend to eliminate. Redesign the process first.
- There is a hard external deadline. End of vendor support or a data center exit may force a lift-and-shift now, with incremental modernization afterward.
- No seam can be made at a reasonable cost. Sometimes the honest answer is a parallel new system with a planned data migration and a rehearsed cutover.
- The data model is the actual problem. If the schema is what is wrong, wrapping the code around it preserves the defect.
Which part of your system could you move first without anyone noticing?
If you can name the slice, the seam and the owner of its data, you are most of the way to a plan. If you cannot, that gap is the first thing worth two weeks of focused work. Trivo maps the slices, the facade and the data ownership with your team and leaves you with a written sequence you own.
Share this article
Keep reading
Strategy · 9 min read
The License Fee Is the Small Number: A Working Guide to Build or Buy
Buying software is usually right, until the product sits on top of how you make money. How to sort capabilities, price the hidden costs and decide.
Oct 12, 2026 Read
Modernization · 9 min read
The ERP stays as it is: building on a decades-old system through an anti-corruption layer
How to build new software on an old ERP without modifying it, using an anti-corruption layer: what goes inside, how to handle writes, when to skip it.
Oct 12, 2026 Read
AI · 9 min read
Bots on the screen or calls to the API: where RPA earns its place and where it becomes debt
RPA and API integration solve the same problem at different layers. How to choose, what screen automation really costs, and how to run it safely if you must.
Oct 12, 2026 Read