On This Page
1The Problem It Solves2Pattern Structure
3When to Use4When Not to Use
5Trade-offs6Implementation Approach
7Anti-Patterns to Avoid8Cloud-Specific Implementations
9References

The Problem It Solves

A legacy system that needs to be replaced cannot safely be rebuilt all at once and switched over in a big-bang migration. The risk is too high — years of production behaviour, edge cases, and implicit business rules cannot be fully captured in a new implementation. Simultaneously, the legacy system cannot remain unchanged indefinitely — it is expensive to maintain, difficult to scale, and blocking new feature delivery.

The Strangler Fig pattern makes the migration incremental, reversible, and testable.

Pattern Structure

%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD START([Client Request]) START --> FACADE[Routing Facade\nReverse proxy or API gateway\nRoutes by path, feature flag,\nor percentage split] FACADE --> DECIDE{Where does\nthis request go?} DECIDE -->|Not yet migrated| LEGACY[Legacy System\nHandles as before\nNo changes to legacy code] DECIDE -->|Migrated capability| NEW[New Service\nMigrated domain\nModern implementation] DECIDE -->|Parallel run| BOTH[Both Systems\nCompare responses\nNew system wins if they differ] LEGACY --> RESPONSE([Response Returned to Client]) NEW --> RESPONSE BOTH --> COMPARE{Responses\nmatch?} COMPARE -->|Yes| RESPONSE COMPARE -->|No| ALERT[Log discrepancy\nAlert engineering team\nReturn new service result] ALERT --> RESPONSE NEW --> DECOM{All traffic\nmigrated?} DECOM -->|Yes| RETIRE[Retire legacy module\nRemove routing rule\nDecommission infrastructure] style START fill:#4f8ef7,color:#fff style RESPONSE fill:#10b981,color:#fff style ALERT fill:#fef3c7 style RETIRE fill:#10b981,color:#fff

When to Use

  • Large legacy systems where a big-bang rewrite is too risky
  • Systems where continuous delivery of new features must continue during the migration
  • Organisations that need to demonstrate value incrementally — migrating the highest-value domains first
  • Applications where the routing facade can be introduced without modifying the legacy system

When Not to Use

  • Small systems where a direct rewrite in a few sprints is lower risk than a multi-year strangler migration
  • Legacy systems that cannot be put behind a routing facade — deep integrations with databases or message queues that cannot be intercepted
  • Situations where the legacy system's data model is so tightly coupled that the facade cannot route at the right granularity

Trade-offs

Benefit Cost
Risk is bounded — each migration increment is small and reversible Two systems running in parallel increases operational complexity
Business value delivery continues throughout the migration Routing facade is a new piece of infrastructure to build and maintain
Each migrated domain can be validated in production before the next Data synchronisation between legacy and new system during transition
Team learns the domain deeply during migration rather than guessing Migration takes longer than a big-bang rewrite would plan for

Implementation Approach

Introduce the facade first. Before migrating any functionality, put a reverse proxy or API gateway in front of the legacy system that passes all traffic through unchanged. Validate that the facade adds no perceptible latency and handles all existing traffic correctly. Only then begin routing specific paths to new services.

Migrate high-value, low-coupling domains first. Identify the capabilities that are most needed in the new system and least entangled with the rest of the legacy system. Authentication, product catalogue, and user profiles are commonly good first migrations. Core transaction processing typically comes last.

Run the shadow mode comparison. For complex migrations, route a copy of production traffic to the new service alongside the legacy system. Compare responses. Log discrepancies. Fix the new service until discrepancy rates are negligible before switching live traffic.

Define done clearly. A strangler migration that is "90% complete" for three years is a failure. Define completion criteria for each capability — what percentage of traffic must be handled by the new system before the legacy module is decommissioned. Execute the decommissioning step on schedule.

Anti-Patterns to Avoid

⚠ 1. Never Decommissioning the Legacy

Successfully routing all traffic to the new service but leaving the legacy system running "just in case". The legacy system continues to consume infrastructure, requires security patching, and acts as a psychological barrier to full commitment to the new architecture.

Hover to see the fix ↻
↺ Correct Approach

Schedule the decommissioning step as a specific sprint deliverable as soon as traffic cutover reaches 100%. Decommissioning is the definition of done for each migrated domain.

⚠ 2. Migrating the Data Model Last

Running the new service against the legacy database schema to avoid a data migration during the initial cutover. The new service inherits all the constraints, naming conventions, and coupling of the legacy schema. The architecture is modern but the data model remains legacy.

Hover to see the fix ↻
↺ Correct Approach

Plan the data migration as part of each domain migration. Migrate the data model alongside the service. Use the facade transition period to synchronise data between the old and new schema.

Cloud-Specific Implementations

  • AWS: An Application Load Balancer with path-based routing rules acts as the routing facade. Feature flags via AWS AppConfig control traffic splits. Route 53 weighted routing enables DNS-level canary migration.

Flowchart

%%{init:{'theme':'base','themeVariables':{'fontSize':'14px','fontFamily':'IBM Plex Sans, system-ui, sans-serif','primaryColor':'#DBEAFE','primaryTextColor':'#1e3a5f','primaryBorderColor':'#2563EB','lineColor':'#374151','clusterBkg':'#F9FAFB','clusterBorder':'#D1D5DB','edgeLabelBackground':'#FFFFFF'},'flowchart':{'curve':'orthogonal','padding':30,'nodeSpacing':65,'rankSpacing':75,'useMaxWidth':true}}}%% flowchart TD START([Client Request Arrives]) START --> FACADE_S[Routing Facade\nAPI Gateway or Reverse Proxy\nNo changes to legacy needed] FACADE_S --> PHASE{Migration Phase} PHASE -->|Phase 1 — Assess| ALL_LEG[All traffic to legacy\nFacade transparent\nBaseline metrics established] PHASE -->|Phase 2 — Shadow| SHADOW[Traffic to legacy\nCopy to new service\nResponses compared — legacy wins] PHASE -->|Phase 3 — Canary| CANARY_S[Small percent to new service\nCompare outcomes\nRollback if errors spike] PHASE -->|Phase 4 — Cutover| NEW_S[All traffic to new service\nLegacy on standby\nMonitor for 2 weeks] PHASE -->|Phase 5 — Retire| DECOM_S[Decommission legacy module\nRemove routing rule\nDelete infrastructure] SHADOW --> MISMATCH{Responses\nmatch?} MISMATCH -->|No| FIX[Fix new service\nRerun shadow phase] MISMATCH -->|Yes| CANARY_S DECOM_S --> DONE_S([Domain Migration Complete]) style START fill:#4f8ef7,color:#fff style DONE_S fill:#10b981,color:#fff style FIX fill:#fef3c7 style FACADE_S fill:#e0f2fe

References

  1. Fowler, Martin — Strangler Fig Application. martinfowler.com/bliki/StranglerFigApplication
  2. Newman, Sam — Monolith to Microservices. O'Reilly, 2019.
  3. Ford, Neal and Parsons, Rebecca — Building Evolutionary Architectures. O'Reilly, 2017.
Ascendion Engineering Knowledge Base ← Deployment Patterns