The strangler fig pattern for legacy migrations

Big-bang rewrites bet everything on a date. The strangler fig pattern wins by routing traffic one endpoint at a time — building the new system around the old until the legacy core is hollow and safe to remove.

Engineering6 min read
Legacy migrationStrangler figSoftware architectureRefactoringDistributed systems
Share

The rewrite was eighteen months in. Launch date arrived. Traffic switched. Three weeks later, traffic switched back. The new system handled happy paths; edge cases lived only in the legacy codebase nobody had fully mapped. Payroll integrations, admin overrides, ten-year-old promo codes — the big-bang bet that a date on a calendar could replace incremental truth. Strangler fig legacy migration takes the opposite bet: route one capability at a time, prove each slice in production, keep rollback trivial until the old system has nothing left to do.

The pattern names a fig that grows around a host tree, eventually replacing it. In software: a facade or router sits in front of the legacy system. New implementations absorb endpoints, events, or data paths. The legacy core shrinks until decommissioning is a small project, not a company-wide risk event.

Big-bang rewrites fail on unknown unknowns

Big-bang riskWhy it compounds
Hidden business rulesOnly surface under production edge cases
Parallel developmentLegacy still changes during rewrite
Integration inventory gapsUndocumented consumers appear at cutover
All-or-nothing rollbackRevert is as risky as launch
Morale cliffLong dark periods before validation

Rewrites feel clean in architecture diagrams. They are messy in ticket backlogs, partner SLAs, and compliance audits. The strangler fig accepts messiness upfront — dual systems, routing complexity, temporary data duplication — in exchange for continuous delivery of value and reversible steps.

A migration without per-step rollback is a rewrite wearing a project plan.

Anatomy of a strangler fig migration

Facade / router. Single entry point for clients — API gateway, reverse proxy, or application router. All traffic passes through. Routing rules decide legacy vs new implementation per endpoint, feature flag, or tenant.

Slice selection. Choose boundaries with clear inputs and outputs — one REST resource, one event type, one batch job. Not "the billing module" — POST /invoices, InvoiceCreated event, nightly reconciliation job.

New implementation. Build behind the facade. Shadow mode optional: new system processes requests but does not respond to clients — compare outputs before cutover.

Traffic shift. Percentage-based, tenant-based, or full cutover per slice. Feature flags control routing without redeploying the facade.

Legacy shrink. Remove routed code from legacy only after sustained proof — metrics green, no rollback for N days, reconciliation clean.

Decommission. When legacy handles zero production traffic and no batch dependencies, retire infrastructure.

Clients → Facade/Router
├─ /users/* → New User Service
├─ /orders/* → New Order Service
├─ /reports/* → Legacy Monolith
└─ /admin/* → Legacy Monolith (last)

The modular monolith startup architecture article describes internal module boundaries that become natural strangler slices — extract a module to a service without rewriting the whole application.

Data migration is the hard part routing does not solve

Routing HTTP traffic is the visible layer. Data often outlives code.

Data patternStrangler approach
Read-heavy sliceNew service reads replica; legacy still writes until cutover
Write migrationDual-write period — both systems accept writes, reconciliation job compares
Event-driven syncLegacy emits change events; new system builds read model
Shared database anti-patternExtract schema slice to new DB with sync; last resort is shared tables with strict ownership

Dual-write periods need reconciliation — compare row counts, checksums, sample diffs on schedule. Divergence discovered at decommission time is expensive. Divergence discovered nightly is manageable.

Never delete legacy data paths until the new path has processed production traffic through a full business cycle — month-end close, billing cycle, compliance reporting window.

Slice ordering strategy

Not all endpoints are equal migration candidates.

Migrate first:

  • High-churn, well-understood domains with good test coverage
  • Read-heavy paths where stale legacy writes are tolerable briefly
  • Features blocking new product direction
  • Endpoints with few undocumented consumers

Migrate last:

  • Admin and override paths with implicit business rules
  • Batch jobs with calendar dependencies
  • Integrations with external partners requiring recertification
  • Areas where "how it works" lives in one engineer's head

Each slice ships independently. Celebrate slice completion — not only final decommission. Morale survives incremental proof.

Facade operations: routing, observability, rollback

The facade is production infrastructure. Treat it accordingly.

  • Routing config as code — versioned rules, reviewed changes, audit trail.
  • Per-route metrics — latency, error rate, traffic split legacy vs new.
  • Instant rollback — flip flag or routing rule to 100% legacy for one slice without affecting others.
  • Request tracing — correlation ID through facade to implementation; essential for shadow mode comparison.

Feature flags as release infrastructure overlap with strangler routing — flags control which implementation serves traffic. The difference is strategic intent: flags gate features; strangler flags gate architectural replacement.

How should teams plan a strangler fig migration?

These questions prevent facades that become permanent complexity.

When is strangler fig better than a rewrite?

When legacy is in production with real users, hidden rules, and integrations that cannot pause. When the team needs to ship new capabilities during migration. When rollback per slice is a requirement. Rewrite may suit greenfield replacement with no production dependency — rare in mature systems.

How small should a slice be?

Small enough to rollback in one routing change. Large enough to deliver meaningful value. Typical: one API resource group, one event consumer, one scheduled job — weeks to months, not days or years.

What stops the facade from becoming permanent?

Track legacy traffic percentage per route. Set decommission criteria upfront: "legacy /orders traffic = 0 for 30 days." Facades without sunset metrics become dual-system operations forever.

A common argument runs the other way

The opposing view holds that maintaining two systems costs more than a fast rewrite — that strangler fig prolongs pain and duplicates effort.

Dual-system cost is real and should be budgeted. Big-bang failure cost — revert, lost months, customer impact — is larger and less predictable. Strangler fig caps downside per slice. The total calendar time may exceed a successful big-bang, but big-bangs that fail do not have a total calendar time — they have a restart.

Teams with limited capacity benefit precisely because slices fit into normal sprints alongside feature work. Rewrites consume the roadmap.

Key takeaways

  • Strangler fig migrates one endpoint or capability at a time behind a facade router.
  • Big-bang rewrites fail on edge cases and integrations discovered only at cutover.
  • Data dual-write and reconciliation matter as much as traffic routing.
  • Migrate well-understood read-heavy slices first; admin and batch jobs last.
  • Per-slice rollback via routing flags keeps migration reversible.
  • Track legacy traffic per route — facades without sunset metrics become permanent.

Conclusion

Legacy migration is not a project with a finish line on a Gantt chart. It is a sequence of small proofs that the new system can carry production truth — one slice at a time, with the old system still standing until each proof holds.

The first step is not writing new code. It is inventory: list every entry point to the legacy system, measure traffic and risk per route, pick the smallest high-value slice, and put a router in front. The fig grows from there.

Related articles

Command Palette

Search for a command to run...