ESC
Type to search guides, tutorials, and reference documentation.
Verified by Garnet Grid

Strangler Fig Pattern

Incrementally replace legacy systems without big-bang migrations. Covers the strangler fig approach, facade routing, feature toggles for migration, data synchronization during transition, and the patterns that let you modernize production systems safely.

A big-bang rewrite is the riskiest project in software engineering. The strangler fig pattern offers a safer alternative: instead of replacing a legacy system all at once, you incrementally build a new system around it. New features go to the new system. Existing features are migrated one by one. The legacy system gradually shrinks until it can be decommissioned.


How the Strangler Fig Works

Phase 1: Facade (route all traffic through a proxy)

  Users


  ┌──────────┐
  │  Facade   │  Routes ALL requests
  │  (Proxy)  │
  └─────┬─────┘


  ┌──────────┐
  │  Legacy   │  Handles everything (status quo)
  │  System   │
  └──────────┘

Phase 2: Strangle (migrate features one by one)

  Users


  ┌──────────┐
  │  Facade   │  Routes based on feature
  │  (Proxy)  │
  └──┬────┬───┘
     │    │
     ▼    ▼
  ┌──────┐ ┌──────────┐
  │ New  │ │  Legacy   │  Still handles some features
  │System│ │  System   │
  └──────┘ └──────────┘

Phase 3: Complete (legacy decommissioned)

  Users


  ┌──────────┐
  │  Facade   │  (Optional: may remove facade too)
  │  (Proxy)  │
  └─────┬─────┘


  ┌──────────┐
  │   New     │  Handles everything
  │  System   │
  └──────────┘

  Legacy system: powered off

Implementation

class StranglerFacade:
    """Route requests between legacy and new systems."""
    
    def __init__(self):
        self.feature_routes = {
            # Feature → which system handles it
            "user_profile": "new",      # Migrated
            "order_create": "new",      # Migrated
            "order_history": "new",     # Migrated
            "inventory": "legacy",      # Not yet migrated
            "reporting": "legacy",      # Not yet migrated
            "billing": "canary",        # Being migrated (split traffic)
        }
    
    def route_request(self, request):
        """Route to legacy or new system based on feature."""
        feature = self.extract_feature(request)
        target = self.feature_routes.get(feature, "legacy")
        
        if target == "new":
            return self.new_system.handle(request)
        
        elif target == "legacy":
            return self.legacy_system.handle(request)
        
        elif target == "canary":
            # Split traffic during migration
            if self.should_canary(request):
                try:
                    new_response = self.new_system.handle(request)
                    legacy_response = self.legacy_system.handle(request)
                    
                    # Compare responses (shadow mode)
                    if new_response != legacy_response:
                        self.log_discrepancy(feature, new_response, legacy_response)
                    
                    return new_response  # Serve from new system
                except Exception:
                    # Fallback to legacy on error
                    return self.legacy_system.handle(request)
            else:
                return self.legacy_system.handle(request)
    
    def migration_status(self):
        """Track overall migration progress."""
        total = len(self.feature_routes)
        migrated = sum(1 for v in self.feature_routes.values() if v == "new")
        return {
            "total_features": total,
            "migrated": migrated,
            "in_progress": sum(1 for v in self.feature_routes.values() if v == "canary"),
            "remaining": sum(1 for v in self.feature_routes.values() if v == "legacy"),
            "progress_pct": migrated / total * 100,
        }

Anti-Patterns

Anti-PatternConsequenceFix
No facade at the startCannot route incrementallyStart with facade before writing any new code
Migrate database firstRiskiest migration as step 1Migrate application logic first, database last
No shadow/canary modeMigration errors discovered in productionShadow mode: run both systems, compare results
Feature migration too largeRisk accumulates, no feedbackSmall feature migrations with quick feedback
Never turn off legacyTwo systems running foreverSet deadline, track progress, decommission

The strangler fig pattern accepts that legacy modernization is risky and proposes the safest possible approach: small, incremental steps with the ability to fall back at any point. The legacy system is not replaced — it is gradually absorbed.

Jakub Dimitri Rezayev
Jakub Dimitri Rezayev
Founder & Chief Architect • Garnet Grid Consulting

Jakub holds an M.S. in Customer Intelligence & Analytics and a B.S. in Finance & Computer Science from Pace University. With deep expertise spanning D365 F&O, Azure, Power BI, and AI/ML systems, he architects enterprise solutions that bridge legacy systems and modern technology — and has led multi-million dollar ERP implementations for Fortune 500 supply chains.

View Full Profile →