Strangle it one route at a time
A façade lets the old and new systems run side by side while you move traffic across a route at a time. Migrate a small shop in the Strangler Lab, catch the differences nobody logged, and switch the old system off without breaking the caller nobody listed.
The riskiest way to replace a system is to build the new one beside it for a year, then switch everything over in one night. Every bug the old system had learned to avoid, and every caller nobody wrote down, arrives at once, and switching back may be impossible by morning.
The alternative has a name. In 2004 Martin Fowler wrote a short post, originally “entitled ‘Strangler Application’”, after the strangler fig: a vine that grows around a host tree until it stands on its own. In 2019 he renamed it Strangler Fig Application, because the short name “takes on a unpleasantly [sic] violent connotation”, and in August 2024 he rewrote the entry as “Strangler Fig”. The idea hasn’t changed: put something in front of the old system, and move its work to the new one a piece at a time, while both keep running.
Four phases, and the last one gets skipped
Microsoft’s Azure Architecture Center gives the clearest sequence:
- Put a façade in front. A proxy or router that every request goes through. “Initially, the façade routes most requests to the legacy system.”
- Move routes across. One at a time, each route’s rule changes from legacy to new. The previous lesson’s shadow lives here: compare first, then move.
- Retire the legacy system. Only once nothing reaches it.
- Remove the façade. “You remove the façade and reconfigure the client app to communicate directly with the new system. This step marks the completion of the migration.” Azure also allows keeping it: “Alternatively, you can maintain the façade as an adapter for legacy clients”.
- The façade goes in first. Clients talk to it and notice nothing, because every rule still points at legacy.
- One route moves. Its rule now points at the new system. Every other route still goes to legacy.
- Both systems are live, so both stores must agree. This is where most of the hard work, and most of the silent failures, live.
- When nothing reaches legacy any more, it can be switched off, and finally the façade itself removed.
The payoff is reversibility. A route that misbehaves goes back to legacy by changing one rule. The cost is what Ian Cartwright, Rob Horn and James Lewis call transitional architecture: the façade, the sync, the comparisons, all of it built to be thrown away. Their advice: “Be explicit and transparent in this ‘value of risk mitigation’ vs ‘cost of transitional architecture’ trade-off.”
What Azure warns about
The Azure page’s list of problems reads like a list of ways this goes quietly wrong:
- “Make sure that the façade keeps up with the migration.” Every route that moves is a rule someone has to change, and a rule that doesn’t match its path does nothing.
- “Make sure that the façade doesn’t become a single point of failure or a performance bottleneck.” Every request now passes through one more hop.
- Data. Once legacy’s tables are dropped, rolling back means “you must restore those objects and replay data changes. However, this process significantly increases effort and risk.”
Keeping two stores in step is the hardest part. Writing to both from the application looks simple, and Martin Kleppmann explains why it isn’t: two writes can land in different orders in the two stores, and “you probably won’t even notice that your database and your search indexes have gone out of sync, because no errors occurred.” Azure’s alternative is “change data capture processing”, “reading the transaction log instead”. AWS says to treat its own queue-based sync “as a tactical solution until you can establish a long-term solution”. Whatever carries the data across is transitional too.
Try it: the Strangler Lab
A small shop, six routes on a façade, and a new system to take them over. The traffic is invented, and each step is one day. The data sync is simplified, so its change data capture is instant and runs both ways, unlike the real thing. Every trap in it is either one a source above documents or one marked as constructed in the debrief.
Two rules hold throughout, and they’re the whole lesson:
- compared always appears next to differed. A route nobody checked is amber and says “not checked”.
- served by is counted per request. It is not read from the mode you picked.
The traps in the lab, and where they come from
- Noise, and the rule that hides a bug. Timestamps and request IDs differ on every request; that’s Shopify’s point from the last lesson. Scrubbing exactly those fields is right. “Ignore all numeric fields” also makes the wrong sale price match, which is the price of a normaliser broader than its evidence.
- Legacy is the buggy side. The search difference is legacy listing a product twice. GitHub’s comparison found “2 serious merge-related bugs in the original Git implementation which had gone undetected for years”. When old and new disagree, either side can be the wrong one.
- The rare bug. One order in five hundred breaks the new code. One day of comparisons misses it. The SRE Workbook: “Terminating a canary deployment after receiving just a handful of queries doesn’t provide a useful signal.” The same is true of a shadow.
- Dual writes. They diverge with no errors, which is Kleppmann’s point exactly. Only comparing the stores shows it.
- The caller nobody listed. Finance’s export runs once a month, has no façade rule, and only appears in legacy’s own access log. Azure’s guidance on retiring anything: “Define clear indicators of inactivity across a full user or business cycle,” and “Disable before deleting.” Switching legacy off is reversible. Deleting it isn’t.
Removal is a production change
The last step looks like housekeeping, and it is where two famous failures happened.
Atlassian’s April 2022 outage began with a script retiring an old app. “Instead of providing the IDs of the intended app being marked for deletion, the team provided the IDs of the entire cloud site where the apps were to be deleted.” Its CTO’s update adds the second half: “The script was executed with the wrong execution mode and the wrong list of IDs.” It ran in permanent-delete mode, not the recoverable one.
Knight Capital’s 2012 loss came from code that should have been removed and wasn’t. When a new release went out, according to the SEC’s order, “one of Knight’s technicians did not copy the new code to one of the eight SMARS computer servers”. That server still held old, unused code, and a reused flag woke it up. Knight “lost over $460 million” in “a 45-minute period”. The lab’s version is smaller: archive before you delete, and don’t delete until a full cycle has gone by with nothing reaching legacy.
You set GET /cart to 'new' on the façade. The new system's dashboards show no cart traffic. What's the first thing to check?
A rule that never matches sends requests to the catch-all, which is legacy. The mode reads 'new' and changes nothing. Count which system served each request, not which mode you picked.
Shadowing GET /search shows legacy returning one product twice where the new system returns it once. What should you do?
A difference only tells you the two disagree. GitHub's comparison found two long-hidden bugs in old Git. Copying legacy's bug into the new system would make the comparison match and the users worse off.
Legacy has been switched off for twelve days and nothing has failed. Why not delete it now?
Azure says to define inactivity 'across a full user or business cycle', and to disable before deleting. A monthly job is invisible for 29 days out of 30. Switching off is the reversible step; deletion is not.
Sources
- Martin Fowler - Strangler Fig (bliki, rewritten 22 Aug 2024)
- Martin Fowler - Original Strangler Fig Application (29 June 2004, renamed 29 April 2019)
- Microsoft Azure Architecture Center - Strangler Fig pattern
- AWS Prescriptive Guidance - Strangler fig pattern
- Ian Cartwright, Rob Horn, James Lewis - Patterns of Legacy Displacement (2021, revised 2024)
- Martin Kleppmann - Using logs to build a solid data infrastructure (or why dual writes are a bad idea), Confluent (2015)
- Microsoft Azure Well-Architected Framework - Safe deployment practices
- Atlassian - Post-incident review, April 2022 outage
- Atlassian - April 2022 outage update (CTO)
- SEC - In the Matter of Knight Capital Americas LLC, Release No. 70694 (16 Oct 2013)
- Vicent Martí - Move Fast and Fix Things, GitHub (15 Dec 2015)
Missions and quizzes above earn their own XP. This marks the lesson done.